Jeremy

Forum Replies Created

Viewing 15 posts - 1,081 through 1,095 (of 1,522 total)
  • Author
    Posts
  • in reply to: Problems inserting locations from table for selector #6230
    Jeremy
    Keymaster

    Hello,

    I think your code is a little more complex than it needs to be, and maybe doesn’t do exactly what you want it to do. Keep in mind that PennController executes the trial’s script in a linear top-down stream unless you use special commands (such as callback). Your design seems perfectly adapted to a linear execution, so I rewrote your code like this:

    Template( variable =>
        newTrial( "trialin" ,
            // Start by creating the image, but don't print it yet
            // Its width will adapt to the screen width (75%)
            newImage("imagefile", variable.Imagein).size("75vw", "auto")
            ,
            newSelector("choice").log()
            ,
            // All images below this will be 2vw x 2vw, hidden, and in the selector
            defaultImage.size("2vw","2vw").hidden().selector("choice")
            ,
            // 75% screen width, 75% screen height
            newCanvas("images", "75vw", "75vh") 
                // Place the main image at the center of the Canvas
                .add("center at 50%", "middle at 50%", getImage("imagefile") )
                // Now place the 6 (hidden) AOIs on the Canvas
                .add("center at 25%", "middle at 70%", newImage("aoiLM", "square.png") ) //left.middle
                .add("center at 75%", "top at 90%",    newImage("aoiRL", "square.png") ) //right low
                .add("center at 80%", "middle at 70%", newImage("aoiRM", "square.png") ) //right middle
                .add("center at 27%", "middle at 90%", newImage("aoiLL", "square.png") ) //left low
                .add("center at 27%", "middle at 40%", newImage("aoiLH", "square.png") ) //left high
                .add("center at 75%", "middle at 40%", newImage("aoiRH", "square.png") ) //right high
                // Finally, place a button at the top of the Canvas
                .add("center at 50%", "middle at 0%",  newButton("start") )
                // Use print here only, it will display the Canvas' (non-hidden) content
                .print()
            ,
            // Script will stay on this line until button is clicked, then remove it
            getButton("start").wait().remove()
            ,
            // Button has been clicked: play the audio
            newAudio("audiofile", variable.Audio).play()
            ,
            // Start mouse-tracking right away (no wait on Audio)
            newMouseTracker("mouse").log().start()
            ,
            // Show this trial's AOIs
            getImage(variable.aoiactive1).visible(),
            getImage(variable.aoiactive2).visible(),
            getImage(variable.aoiactive3).visible()
            ,
            // Script will stay on this line until an AOI is clicked
            getSelector("choice").wait()
            ,
            // Stop the mouse tracker once an AOI is clicked
            getMouseTracker("mouse").stop()
            ,
            // Stop the audio early in case it hasn't fully played yet
            getAudio("audiofile").stop()
        )//trial end
    )//template end

    And here are the first two lines of my table:

    Audio,Imagein,aoiactive1,aoiactive2,aoiactive3
    audio.wav,lees.png,aoiLH,aoiRM,aoiRL

    Hopefully the script and setup above are self-explanatory, but please let me know if you have questions

    Jeremy

    in reply to: Choosing subet of items to present #6225
    Jeremy
    Keymaster

    Hi Maria,

    I’m not sure what is happening with the picture not disappearing from the page: when I test your code, the picture always disappears as it should. I could take a closer look if you sent me a link to your experiment at support@pcibex.net

    The Selector element doesn’t quite work the way you’re using it, because PennController trials execute their script in a linear top-down order: your script runs play on the sg_voicing Audio element first, then it later runs play on the sg_devoicing Audio element, and only after that does it reach the shuffle command of the Selector element, but by then the audios have already played. As a result, all your trials will play sg_voicing first and sg_devoicing second.

    Another important point to note is that using shuffle and then keys will result in a non-deterministic association order-wise. See the command’s documentation page for more detail.

    What you can do however is test the index of the Selector’s elements after shuffling, and decide which to play first based on that, and also properly associate the keys depending on which is first and which is second. Here is a proposal (see notes below):

    const alt_test_trial = variable => [
        newTimer(500)
            .start()
            .wait()
        ,
        newImage("pl_picture", variable.PlPictureFile)
            .size(300,200)
            .print()
        ,
        newTimer(200)
            .start()
            .wait()
        ,
        newAudio("pl_voicing", variable.PlVoicingFile)
            .play()
        ,
        newTimer(2000)
            .start()
            .wait()
        ,
        getImage("pl_picture")
            .remove()
        ,
        newImage("sg_picture", variable.SgPictureFile)
            .size(200,200)
            .print()
        ,
        newTimer(200)
            .start()
            .wait()
        ,
        newAudio("sg_voicing", variable.SgVoicingFile),
        newAudio("sg_devoicing", variable.SgDevoicingFile)
        ,
        newSelector("target")
            .log()
            .add( getAudio("sg_voicing"),getAudio("sg_devoicing") )
            .shuffle()
            .test.index( getAudio("sg_voicing"), 0 )
            .success(
                getAudio("sg_voicing").play().wait(),
                getAudio("sg_devoicing").play().wait(),
                getSelector("target").keys("1","2").wait()
            )
            .failure( 
                getAudio("sg_devoicing").play().wait(),
                getAudio("sg_voicing").play().wait(),
                getSelector("target").keys("2","1").wait()
            )
        ,
        newTimer(500)
            .start()
            .wait()
    ]

    As you can see, I use test.index to see which audio occupies the Selector’s first slot after shuffling, and I accordingly play them in that order, and associate the 1 and 2 keys relative to that order too. Another thing you’ll note is that I use wait on the Audio elements rather than creating a Timer element of a given duration. Of course feel free to go back to the Timer method if that’s more appropriate to your design’s needs.

    One other thing: your last two log commands should attach to the closing parenthesis of newTrial, so they cannot be part of those const definitions. Instead, they should go directly inside the Template command, between its closing parenthesis and that of newTrial (see example below)

    Regarding the 75-25 split in your testing trials, you cannot assign them different labels for the same reason that you couldn’t assign different labels to your training and testing trials: you generate them all from the same rows, and the distribution of your trial types takes scope over all of them. If you were to break them into different labels (ie. different Template/newTrial commands) you would break the solution we’ve come up with to deal with the dependency between training and testing trials.

    The solution I’ll propose here is similar to what we did for testing vs training: creating a global Var element to switch between types of trials. In this case, we’ll switch between singular target and plural target. In order to have 25% of the trials with a singular target and 75% of them with a plural target, we’ll use pick twice on the set of all the testing trials, once with a 25% proportion and once with a 75% proportion, and we’ll precede the trials from each subset with another trial switching to singular or plural target. Here is how to do that, illustrated with “alt” items only:

    alt = randomize("alt")
    picked_alt = pick(alt, 29)
    repeat_alt = pick(randomize(picked_alt), 15) // 15 testing items shown in training
    new_alt = pick(alt, 15) // 15 new trials in testing
    testing_alt = randomize(seq(repeat_alt,new_alt)) // Merge and randomize the 30 test trials
    
    Sequence( 
      "intro",
      rshuffle(picked_alt),
      "transition",
      rshuffle(
        randomize(seq( 
            // Pick 22/30 test trials (~75%) and precede with pl
            precedeEachWith("plTarget", pick(testing_alt, 22) ),
            // Pick 8/30 test trials (~25%) and precede with sg
            precedeEachWith("sgTarget", pick(testing_alt, 8) ) 
        ))
      )
    )
    
    // Minimal example showing how to test "target"
    const alt_test_trial = variable => [ 
        getVar("target").test.is("pl")
            .success( newText("Plural").print() )
            .failure( newText("Singular").print() )
        ,
        newText("Test trial").print()
        ,
        newButton("Next").print().wait() 
    ]
    // Minimal example showing the absence of "target" in training
    const alt_training_trial = variable => [ 
        newText("Training trial").print()
        ,
        newButton("Next").print().wait() 
    ]
    
    // Make sure to define "target" in intro
    newTrial("intro", 
        newVar("phase", "training").global() ,
        newVar("target").global() ,
        newButton("Start").print().wait()
    )
    
    // Each trial sets "target" to the appropriate value
    newTrial("plTarget", getVar("target").set("pl") )
    newTrial("sgTarget", getVar("target").set("sg") )
    
    newTrial("transition", getVar("phase").set("test") )
    
    // This is a minimal example too
    Template( row => 
      newTrial( "alt" ,
        getVar("phase").test.is("training")
            .success( ...alt_training_trial(row) )
            .failure( ...alt_test_trial(row) )
      )
      .log( "Item" , row.Item )
    )

    I must say your design is particularly challenging, with all those distributional requirements! Let me know if you have questions

    Jeremy

    • This reply was modified 4 years, 8 months ago by Jeremy.
    in reply to: Displaying images based on participants' previous selections #6223
    Jeremy
    Keymaster

    Hi,

    The cleanest method in this case is probably to add a column to your table that identifies your items. It can be a number, or a string, whatever makes the most sense to you. This way, you can create a specific global Var element for each of your learning trials, that you look up during the testing phase. It will work as long as you generate the test trials from the same rows as the training trials (or at least rows that reference the same identifying value).

    Here is a short example. I use a table with a column named ItemCode, and use the value of that column to name global Var elements specific to that item:

    AddHost("https://raw.githubusercontent.com/PennController/TimedPictureSelection/master/chunk_includes/")
    
    AddTable("myTable", `ItemCode,SgImage,PlImage
    deer,1deerDenseWood.png,2deerSparseWood.png
    fish,1fishSquareTank.png,2fishRoundTank.png
    moose,1mooseNewPark.png,2mooseOldPark.png
    sheep,1sheepRedPen.png,2sheepBluePen.png`)
    
    Template( row => newTrial( "choose" ,
        newText("Choose an image").print()
        ,
        newCanvas("images", 500, 400)
            .add(  0,0,newImage("sg", row.SgImage).size(200,200))
            .add(300,0,newImage("pl", row.PlImage).size(200,200))
            .center()
            .print()
        ,
        newVar("choice_"+row.ItemCode).global()
        ,
        newSelector("answer")
            .add( getImage("sg") , getImage("pl") )
            .shuffle()
            .wait()
            .test.selected(getImage("sg"))
            .success( getVar("choice_"+row.ItemCode).set('sg') )
            .failure( getVar("choice_"+row.ItemCode).set('pl') )
    ) )
    
    Template( row => newTrial( "see" ,
        newText("Here is the image you chose").print()
        ,
        getVar("choice_"+row.ItemCode)
            .test.is('sg')
            .success( newImage(row.SgImage).size(200,200).center().print() )
            .failure( newImage(row.PlImage).size(200,200).center().print() )
        ,
        newButton("OK").print().wait()
    ) )

    Let me know if you have questions

    Jeremy

    in reply to: error in uploading materials (csv file) #6219
    Jeremy
    Keymaster

    You’re now under the 64MB quota (at 25MB to be precise) so I took your account off the list, you should be able to edit your projects as before

    Best,
    Jeremy

    in reply to: error in uploading materials (csv file) #6217
    Jeremy
    Keymaster

    Hi August,

    Actually your account does exceed the 64MB quota, with most of the space used corresponding to results files. Download those files and save copies somewhere safe, then delete them from the farm. Let me know once you’re done so I can take your account off the exceed-quota list.

    Sorry for the inconvenience,
    Jeremy

    in reply to: Choosing subet of items to present #6212
    Jeremy
    Keymaster

    If you don’t use randomize, you need to use seq to convert the label string into a sequence of trials. Then, pick will always pick the first trial(s) in that sequence, because you won’t have applied randomization first.

    The number of trials you pick is determined by the number you pass to pick and is not related to whether you randomized the trials or not. So if you do this:

    trials = seq("trials")
    Sequence( pick(trials,1), "break", pick(trials,1), "break", pick(trials,1) )
    
    newTrial("trials", newButton("Trial 1").print().wait() )
    newTrial("trials", newButton("Trial 2").print().wait() )
    newTrial("trials", newButton("Trial 3").print().wait() )
    
    newTrial("break", newButton("Break").print().wait() )

    You’ll always see Trial 1 first, then the break trial, then Trial 2, then the break trial again, and finally Trial 3. If you replaced seq with randomize in the code above, you’d still only have one trial before/after the break trials, but the order would be random, eg. Trial 3 – Break – Trial 1 – Break – Trial 2

    Jeremy

    in reply to: Choosing subet of items to present #6211
    Jeremy
    Keymaster

    Sorry, I realize now that I wasn’t very clear when I typed // code your '...' trials here. If you look back at this previous message, you’ll notice that the content of those const variables, ie. what goes inside the [ ] brackets, does not contain Template or newTrial, but only what normally goes inside the parentheses of newTrial.

    So in this specific case, you should have:

    const alt_training_trial = variable => [
      newTimer(500)
          .start()
          .wait()
      ,
      newImage("sg_picture", variable.SgPictureFile)
          .size(200,200)
          .print()
      ,
      newTimer(200)
          .start()
          .wait()
      ,
      newAudio("sg_voicing", variable.SgVoicingFile)
          .play()
      ,
      newTimer(1500)
          .start()
          .wait()
      ,
      getImage("sg_picture")
          .remove()
      ,
      newTimer(1000)
          .start()
          .wait()
      ,
      newImage("pl_picture", variable.PlPictureFile)
          .size(300,200)
          .print()
      ,
      newTimer(200)
          .start()
          .wait()
      ,
      newAudio("pl_voicing", variable.PlVoicingFile)
          .play()
      ,
      newTimer(2000)
          .start()
          .wait()
      ,
      getImage("pl_picture")
          .remove()
      ,
      newTimer(200)
          .start()
          .wait()
      ,
      newButton("Neste")
          .print()
          .wait()
    ]

    With this corresponding Template command further down in your script (I’m assuming you’ve also defined const alt_test_trial following the same schema as above):

    Template( defaultTable.filter(r => r.Type=="alt" ) , row =>
      newTrial( "alt" ,
        getVar("phase").test.is("training")
            .success( ...alt_test_trial(row) )
            .failure( ...alt_training_trial(row) ) 
      )
      .log("phase", getVar("phase") )
      // You might want to add more logs
    )

    Let me know if you have questions

    Jeremy

    in reply to: Server setup issues #6208
    Jeremy
    Keymaster

    Hi,

    I’ve never needed to run mkdist.sh when setting up a CGI server, so you can probably just stop that process and not worry about the errors it returns. As long as the python file runs fine and you’re able to run your experiment and collect data, your setup should be configured properly.

    That being said, I’ve never had to manually run the python file myself either. You’re on Dreamhost, so your webserver should be apache, and it should already be configured to run CGI scripts. If not, search something like “apache CGI” and you should find some relevant documentation—when everything’s set up, simply visiting the experiment.html file of your experiment automatically runs server.py in the background. For what it’s worth, I don’t think I’ve ever read that README, so I’m not sure how relevant it is

    Jeremy

    • This reply was modified 4 years, 8 months ago by Jeremy.
    in reply to: DashedSentence in a PennController trial #6207
    Jeremy
    Keymaster

    Hi,

    If by “cumulative” you mean that the words that have already been read should not be replaced by underscores when you’re reading the next words, replacing n==i with n<=i should be all you need to do

    Jeremy

    in reply to: Choosing subet of items to present #6203
    Jeremy
    Keymaster

    Hi Maria,

    Based on your previous messages, I think you would do something like this for your specific design:

    alt = randomize("alt")
    non = randomize("non")
    fil = randomize("fil")
    
    picked_alt = pick(alt, 29)
    picked_non = pick(non, 17)
    picked_fil = pick(fil, 18)
    
    repeat_alt = pick(randomize(picked_alt), 15)
    repeat_non = pick(randomize(picked_non), 5)
    repeat_fil = pick(randomize(picked_fil), 10)
    
    Sequence( 
      "intro",
      rshuffle(picked_alt, picked_non, picked_fil),
      "transition",
      rshuffle(
        repeat_alt, repeat_non, repeat_fil, 
        pick(alt,15), pick(non,5), pick(fil,5) // Don't know how many fillers are left
      )
    )
    
    
    newTrial("intro", newVar("phase", "training").global() )
    newTrial("transition", getVar("phase").set("test") )
    
    
    const alt_training_trial = row => [
      // code your 'alt' training trials here
    ]
    const non_training_trial = row => [
      // code your 'non' training trials here
    ]
    const fil_training_trial = row => [
      // code your 'fil' training trials here
    ]
    
    
    const alt_test_trial = row => [
      // code your 'alt' test trials here
    ]
    const non_test_trial = row => [
      // code your 'non' test trials here
    ]
    const fil_test_trial = row => [
      // code your 'fil' test trials here
    ]
    
    
    Template( "alt_table , row =>
      newTrial( "alt" ,
        getVar("phase").test.is("training")
            .success( ...alt_test_trial(row) )
            .failure( ...alt_training_trial(row) ) 
      )
      .log("phase", getVar("phase") )
      // You might want to add more logs
    )
    Template( "non_table , row =>
      newTrial( "non" ,
        getVar("phase").test.is("training")
            .success( ...non_test_trial(row) )
            .failure( ...non_training_trial(row) ) 
      )
      .log("phase", getVar("phase") )
      // You might want to add more logs
    )
    Template( "fil_table , row =>
      newTrial( "fil" ,
        getVar("phase").test.is("training")
            .success( ...fil_test_trial(row) )
            .failure( ...fil_training_trial(row) ) 
      )
      .log("phase", getVar("phase") )
      // You might want to add more logs
    )

    I didn’t test this code, but hopefully it should give you the idea

    Let me know if you have any questions

    Jeremy

    in reply to: Choosing subet of items to present #6200
    Jeremy
    Keymaster

    Hi,

    Do I understand you correctly that you have rows in your table from which you generate both training and test trials, so that each row is associated with two trials (one training, one test) and that you want to show some training trials in the training phase, and in the test phase, you want to select half of those training trials but show the corresponding test trial instead (ie. the one that was generated from the same row, but using a different template) and then show the test trials corresponding to the training trials that were not shown in the training phase?

    If that’s what you want to do, the problem with the current solution is that you generate separate training and test trials (even if generated from the same rows) so that the trials picked by the pick function will either be the training or the test ones, and so applying pick again later will not exclude the dual trials of the type that was not picked before.

    From here, you have two options: either you generate a single trial for each row, whose content can dynamically switch between training or test depending on as part of which phase you’re running it, or you give up on the pick function and manually use arrays for the specific needs of your design. The latter option requires good knowledge of javascript and how (PC)Ibex generates and orders its trials though.

    If you decide to go with the former option (if feasible at all with your design), you could do something like this:

    Sequence( "intro", "trials", "transition", "trials" )
    
    AddTable("table", "Text\nhello\nworld\nbye\ntown")
    
    newTrial("intro", newVar("phase", "training").global() )
    newTrial("transition", getVar("phase").set("test") )
    
    const test_trial = row => [
      newText("test "+row.Text).print()
      ,
      newButton("Next").print().wait()
    ]
    
    const training_trial = row => [
      newText("training "+row.Text).print()
      ,
      newButton("Next").print().wait()
    ]
    
    
    Template( "table" , row =>
      newTrial( "trials" ,
        getVar("phase").test.is("training")
            .success( ...test_trial(row) )
            .failure( ...training_trial(row) ) 
      )
      .log("phase", getVar("phase") )
    )

    This way, you have a single label (trials in my example) for the two versions of each item, and you can use pick as intended (not illustrated above)

    Let me know if you have questions

    Jeremy

    in reply to: Choosing subet of items to present #6198
    Jeremy
    Keymaster

    Hi Maria,

    Since my last reply on this thread, I came up with another pretty useful function, pick, that I introduced on that thread. It makes it easier to randomize a set of trials and pick just a subset of it, and maybe even pick some more from the remaining trials later on if your design requires it (which your current design does require, if I understand correctly). You also no longer have to use GetTable().filter or define those number variables.

    Your current design has one more requirement though, which is to retrieve the trials that you previously picked, and from that retrieval set, pick again a random subset of trials. So I will propose a new version of the pick function here, which will allow you to embed pick recursively (since you need to pick from a picked set). To be clear, you should use the code below, not the one from the linked thread—you can place it at the top of your main script for example:

    function Pick(set,n) {
        assert(set instanceof Object, "First argument of pick cannot be a plain string" );
        n = Number(n);
        if (isNaN(n) || n<0) 
            n = 0;
        this.args = [set];
        this.runSet = null;
        set.remainingSet = null;
        this.run = arrays => {
            if (this.runSet!==null) return this.runSet;
            const newArray = [];
            if (set.remainingSet===null) {
                if (set.runSet instanceof Array) set.remainingSet = [...set.runSet];
                else set.remainingSet = arrays[0];
            }
            for (let i = 0; i < n && set.remainingSet.length; i++)
                newArray.push( set.remainingSet.shift() );
            this.runSet = [...newArray];
            return newArray;
        }
    }
    function pick(set, n) { return new Pick(set,n); }

    Then you can use it like this, for example:

    fillers = randomize("filler")
    tests = randomize("test")
    
    picked_fillers = pick(fillers, 4)
    picked_tests = pick(tests, 3)
    
    repeat_fillers = pick(randomize(picked_fillers), 2)
    repeat_tests = pick(randomize(picked_tests), 2)
    
    Sequence(
        picked_fillers/*4 trials*/, picked_tests/*3 trials*/,
        "break",
        repeat_fillers/*2 trials*/ , repeat_tests/*2 trials*/ , pick(fillers,1)/*1 trials*/ , pick(tests,2)/*2 trials*/
    )

    Some explanations:

    1. I first randomize all the trials labeled “filler” and all the trials labeled “test”, and I store the result in variables that I name fillers and tests, respectively
    2. Then I use pick on those variables, so as to pick the first four and the first three trials from those randomized sets, and store the picked trials in picked_fillers and picked_tests, respectively. After that, the sets stored in the variables fillers and tests have lost four and three trials, respectively
    3. Then I use pick on the (randomized) variables picked_fillers and picked_tests, and store the result in repeat_fillers and repeat_tests, respectively. You need to randomize here, because otherwise you would always pick the first trials from the sets in the picked_ variables, which would effectively result in systematically repeating the oldest (=first) trials from the first half of the experiment
    4. Finally, I build my Sequence using those variables, and also use pick one last time on fillers and tests again which, at that point, no longer contain the trials that were transferred to picked_fillers and picked_tests in the meantime, so we’re sure that by doing so, we’re not repeating trials that were already presented in the first half

    If you need further randomization, you can use rshuffle (e.g. rshufflle(repeat_fillers,repeat_tests,pick(fillers,1),pick(tests,2))) or randomize, but keep in mind that the latter only accepts single arguments, so you would need to also use seq in this case: randomize( seq(repeat_fillers,repeat_tests,pick(fillers,1),pick(tests,2)) )

    Let me know if you have questions

    Jeremy

    in reply to: Unpredictably stuck in preloading #6195
    Jeremy
    Keymaster

    Good catch! It was also listed on the troubleshooting help page, actually, I guess I should’ve applied my own medicine before posting my message

    Jeremy

    in reply to: Saving results #6191
    Jeremy
    Keymaster

    Sounds like a browser-specific issue. Try saving the file using Firefox or Safari, it worked for someone who faced a similar problem using Chrome

    Jeremy

    in reply to: Saving results #6189
    Jeremy
    Keymaster

    The format is actually CSV: the content definitely contains Comma-Separated Values. Just rename your file after you save it, and change its .txt extension for .csv

    Jeremy

Viewing 15 posts - 1,081 through 1,095 (of 1,522 total)