Jeremy

Forum Replies Created

Viewing 15 posts - 766 through 780 (of 1,522 total)
  • Author
    Posts
  • in reply to: HTML Files Uploaded to Chunk Includes #7183
    Jeremy
    Keymaster

    Ah, yes, thank you, there is indeed a 50-character limit on filenames!

    Glad you figured it out, and that you posted in this forum, it will surely be helpful to anyone who faces the same issue in the future

    Jeremy

    in reply to: HTML Files Uploaded to Chunk Includes #7180
    Jeremy
    Keymaster

    I currently see 7 html files in your project’s Resources folder: buffer1.html, buffer2.html, buffer3.html, endangered_animals_at_a_glance_questions.html, energetic_emily_questions.html, engineering_and_natural_gas_questions.html, verification.html

    When I copy your project and create a new HTML file using the ‘+’ button next to “Resources”, or upload one by drag-and-dropping it there (or using the upload icon and selecting a file from my device) I do get the “upload to chunk_includes” confirmation message and the file appears under Resources

    Note that there is a “filter…” field under folders’ names where you can type text to only show filenames that contain that text — make sure you haven’t any text currently typed in there when looking for the uploaded files

    If the problem persists, try duplicating your project and uploading your HTML files to that cloned project; if upload succeeds, delete the original project and continue working with the cloned project (you can rename it to use the original project’s name then)

    Jeremy

    in reply to: HTML Files Uploaded to Chunk Includes #7178
    Jeremy
    Keymaster

    Hello Jack,

    I am not familiar with this problem. Were you logged in when this happened? Would you mind sharing your project’s slug with me (the 6-character sequence between experiments/ and /edit in the URL when you’re editing the project)?

    Jeremy

    in reply to: Diagonal scale labels #7176
    Jeremy
    Keymaster

    Hello Matthias,

    Alignment will be easier to handle if you set the first scale’s labels using the .label command and pass it your Text elements: this way, the Text elements will be positioned relative to the radio bullets

    Once you have that, it’s a matter of playing with the CSS rules, in particular transform and transform-origin. Here is a suggestion on how to achieve what you’re after (note that I also added one CSS rule to Aesthetics/global_main.css to add spacing between the scales’ options):

    https://farm.pcibex.net/r/EAIBDr/

    Let me know if you have questions

    Jeremy

    in reply to: Server setup #7173
    Jeremy
    Keymaster

    Maybe your webserver does not automatically execute CGI scripts then, would you mind sharing your experiment’s URL with me, here or at support@pcibex.net?

    Jeremy

    in reply to: Server setup #7171
    Jeremy
    Keymaster

    Hi Nasim,

    If you’re going with a CGI setup, you do not need to run any script yourself: your webserver will take care of doing it itself when you visit your experiment’s page in a browser

    Do you get an error when you visit your experiment’s page?

    Jeremy

    in reply to: Event segmentation task #7168
    Jeremy
    Keymaster

    Hello Diana,

    You could use a Function element to detect seeking events on the video and perform operations accordingly

    Here is a suggestion — I also reworked the print logic, because the visual arrangement appeared off for me

    Template ("all_4_no_interference_fam_lang_task.csv",
        row => newTrial("segmentation_task",
            defaultText.css({'white-space': 'nowrap', margin: '0.5em'}).center()
            ,
            newText("info1", "<p>If you were to divide this movie into meaningful events, at which second would the first meaninful event intuitively end?</p>")
                .bold()
                .print()
            ,
            newTimer("wait", 3000).start().wait()
            ,
            newVideo("video", row.video)
                .size("40vw", "23vw")
                .print()
                .play()
                .wait()
                .log()
            ,
            newTooltip("tips", "Use the navigation bar to provide your answer")
               .label("Okay")
               .position("right middle")
               .size(200,"auto")
               .print( getVideo("video") )
            ,
            newText("lenght_label", "The natural ending for the first meaningful event would be at: ")
                .after( newText("seconds_label1", "...") )
                .print()
            ,
            newVar("ending times", [0]).log()   // This .log() is somewhat redundant with video.log()
            ,
            newFunction( ()=>new Promise(r=>getVideo("video")._element.video.addEventListener("seeking", async e=>{
                await getVar("ending times").set(v=>[...v.slice(0,-1),e.target.currentTime])._runPromises();
                await getText("seconds_label"+getVar("ending times").value.length).text(
                    Math.round(e.target.currentTime) + " seconds."
                )._runPromises();
                r();
            })) ).call()
            ,
            newDropDown("slider2", "(Select an option)")
                .add("Yes","No")
                .before(newText("question", "Do you think a new event begins right after the previous one?") )
                .cssContainer("margin", "0.5em")
                .center()
                .print()
                .log()
                .wait()
                .test.selected("Yes")
                    .success(
                        getTooltip("tips").print( getVideo("video") )
                        ,
                        getVar("ending times").set(v=>[...v,[0]])
                        ,
                        newText("lenght_label", "The next meaningful event begins at: ")
                            .after( newText("seconds_label2", "...") )
                            .print()
                        ,
                        newFunction(()=>new Promise(r=>getVideo("video")._element.video.addEventListener("seeking", r))).call()
                    )
            ,
            newText("instructions", "Please briefly describe the contents of the video!")
                .print()
            ,
            newTextInput("description", "")
                .log()
                .lines(0)
                .size(500, 100)
                .center()
                .print()
            ,
            newButton("Next")
                .css({
                    "background-color": "green",
                    "font-size": "24px",
                    "border-radius": "15px",
                    padding: "15px 25px",
                    "text-align": "center",
                    color: "#fff",
                    border: "none"
                })
                .center()
                .print()
                .wait()
            )
            .log("condition",row.condition)
            .log("name",row.name)
            .log("goal",row.goal)
            .log("event",row.event)
            .log("bin",row.bin)
            .log("video",row.video)
    )
    

    Jeremy

    in reply to: words showing on separate lines in self-paced rading #7166
    Jeremy
    Keymaster

    Yes, sure, try it and keep it like this if you like it!

    Jeremy

    in reply to: words showing on separate lines in self-paced rading #7163
    Jeremy
    Keymaster

    Dear August,

    What you can do is execute some javascript immediately after you print your DashedSentence to wrap your sentences in an element that won’t allow linebreaks:

    newController("DashedSentence", {s : "You have just begun reading the sentence. You have just finished reading."})
        .print()
    ,
    newFunction(()=>{
        const container = document.querySelector(".DashedSentence-sentence");
        const sentences = [[]];
        container.childNodes.forEach(n=>{
            if (sentences[0].length || n.nodeName!="#text")
            sentences[0].push(n);
            if ($(n).text() && $(n).text().match(/[.!?]$/))
                sentences.unshift([]);
        });
        while (sentences.length){
            const sentence = sentences.pop();
            if (sentence.length===0) continue;
            const span = document.createElement("SPAN");
            span.style['margin-right'] = "0.5em";
            span.style['white-space'] = "nowrap";
            span.append(...sentence);
            container.append(span);
        }
        container.style.display = 'flex';
        container.style['flex-wrap'] = 'wrap';
    }).call()
    ,
    getController("DashedSentence")
        .wait()
        .remove()

    Notice the sequence [.!?], which defines the characters that you consider to constitute sentence delimiters. Feel free to remove or add characters to that (eg. ;)

    Jeremy

    in reply to: Key press highlights wrong image #7158
    Jeremy
    Keymaster

    Hi Daniela,

    Removing the Canvas element from the page does not disable the Selector element: it is still active, even if the elements it contains are not visible on the page

    Here’s an edited version of your code. Most of the edits are aesthetic; the crucial bit is the default .once() on the Selector elements, which prevents them from continuing to record keypresses after the initial choice:

    Template( 
      GetTable( "stimuli.csv")// change this line for the appropriate experimental list
          .filter("type" , "critical")
          .filter("lifetime" , /^(dead|alive)$/)
      ,
      variable => newTrial( "post_task" ,
        defaultText
            .css({"font-family":"courier","font-size":"25px"})
            .center()
        ,
        defaultSelector
            .once()
            .log()
        ,
        defaultCanvas
            .log()
            .center()
        // NEW TEXT
        ,
        newText("post_name",  variable.name)
        ,
        newText("occupation_correct", variable.occupation)
        ,
        newText("occupation_incorrect", variable.occupation_distractor)
        ,
        newText("nationality_correct",  variable.nationality)
        ,
        newText("nationality_incorrect",  variable.nationality_distractor)
        ,
        newText("lifetime_correct", variable.lifetime)
        ,
        newText("lifetime_incorrect", variable.lifetime_distractor)
        ,
        newImage("checkmark", "https://amor.cms.hu-berlin.de/~pallesid/dfg_pretests/pictures/checkmark.jpeg").size(30,30)
        ,
        newImage("crossmark", "https://amor.cms.hu-berlin.de/~pallesid/dfg_pretests/pictures/crossmark.png").size(30,30)
        ,
        // NAME
        newCanvas("name", "100vw" , "100vh")
            .add("center at 50%", "center at 20%", getText("post_name"))
            .add("center at 25%", "center at 20%", getImage("checkmark") )
            .add("center at 75%", "center at 20%", getImage("crossmark") )
            .print()
        ,
        newSelector("post_name")
            .add(getImage("checkmark"), getImage("crossmark"))
            .keys("F", "J")
            .wait()
        ,
        getCanvas("name").remove()
        ,
        // LIFETIME
        newCanvas("lifetime", "100vw" , "100vh")
            .add( "center at 30%", "center at 20%", getText("lifetime_correct"))
            .add( "center at 70%", "center at 20%", getText("lifetime_incorrect"))
            .print()
        ,
        newSelector("post_lifetime")
            .add(getText("lifetime_correct"), getText("lifetime_incorrect"))
            .shuffle()
            .keys("F", "J")
            .wait()
        ,
        getCanvas("lifetime").remove()
        ,
        // NATIONALITY
        newCanvas("nationality", "100vw" , "100vh")
            .add( "center at 30%", "center at 20%", getText("nationality_correct"))
            .add( "center at 70%", "center at 20%", getText("nationality_incorrect"))
            .print()
        ,
        newSelector("post_nationality")
            .add(getText("nationality_correct"), getText("nationality_incorrect"))
            .shuffle()
            .keys("F", "J")
            .wait()
        ,
        getCanvas("nationality").remove()
        ,
        // OCCUPATIION
        newCanvas("occupation", "100vw" , "100vh")
            .add(  "center at 30%", "center at 20%", getText("occupation_correct"))
            .add("center at 70%", "center at 20%", getText("occupation_incorrect"))
            .print()
        ,
        newSelector("post_occupation")
            .add(getText("occupation_correct"), getText("occupation_incorrect"))
            .shuffle()
            .keys("F", "J")
            .wait()
        ,
        getCanvas("occupation").remove()
        ,
        // WAIT
        newCanvas("dots", "100vw" , "100vh")
            .add("center at 50%", "center at 20%", newText("pleasewait_post2", "...").bold())
            .print()
        ,
        newTimer("wait_post2", 1000)
            .start()
            .wait()
        ,
        getCanvas("dots").remove()
      )
      .log("type", variable.type)
      .log("lifetime" , variable.lifetime)
      .log("tense", variable.tense)
      .log("mm", variable.mm)
      .log("match", variable.match)
      .log("rating", getVar("rating"))
      .log("item" , variable.item_id)
      .log("name" , variable.name)
      .log("list", variable.list)
      .log( "withsquare", GetURLParameter("withsquare") )    
      .log("bare_verb", variable.bare) 
    )
    

    Jeremy

    in reply to: Server setup #7155
    Jeremy
    Keymaster

    Hi Nasim,

    The documentation is a little confusing about this, but setting up an experiment on a webserver is actually simpler than that, assuming your server readily runs CGI Python scripts (most do).

    You should follow the step-by-step CGI instructions. The references to webspr are outdated, but in any case, if you don’t rename any file, the only thing you need to edit (if I remember correctly) is line 20 of server_conf.py to SERVER_MODE = "cgi" (so only bullet 5 from the documentation should be relevant). Then your experiment will be accessible at https://your.domain/path/experiment.html and automatically served on port 80/443 (depending on whether you’re on http or https)

    The “stand-alone” mode from the Ibex documentation refers to running the experiment locally, that is, not on a webserver. I sometimes use it when I develop experiments on my own computer, I open a terminal, navigate to the www folder of my project, type python server.py, then I open my browser and visit http://localhost:3000/experiment.html and there I can see my experiment running

    I have never needed to run sh mkdist.sh

    Let me know whether you were able to run your experiment

    Jeremy

    in reply to: recordings not saving #7152
    Jeremy
    Keymaster

    I get a CORS error when I take your experiment. This means your server does not return the necessary CORS headers. When I try to access your website (agnesygao.com) I get a security warning, informing me that your certificate likely expired. This would explain why the CORS request fails, as your website is not considered secure. If your SSL certificate is expired, you should renew it, and see whether uploads succeed again then

    Three additional notes:

    – the URL you pass to InitiateRecorder points to a PHP file, so in this case it doesn’t really matter that it is an “amazon server”. I thought you were using an API to process the request, as described in this guide

    – I strongly encourage you to insert asynchronous UploadRecordings (passing "noblock", ) at regular intervals in your experiment in addition to your final (synchronous) UploadRecorings trial: you have over 700 trials, with one recording per trial, which can rapidly make for a large archive of recordings to upload all at once at the very end of your experiment. Even after fixing the CORS issue, you might still have to wait for several (tens of) minutes on the “Please wait” message before the upload completes. You could include an asynchronous UploadRecordings after each block, for example, so that when you reach the end of your experiment, there is only one block of recordings left to upload

    – As far as I can tell, you are unnecessarily duplicating a Template piece of code 12 times, with the only differences being which CSV file you use as a table, and which label you assign to the output trials. My advice is you concatenate all 12 CSV files into a single file, to which you add a “Label” column which will read “block1”, “block2”, …, “block12” for the corresponding rows. Then you can keep a single Template command that will start like this:

    Template( "L1_all.csv" ,
         row => newTrial( row.Label ,
    

    Jeremy

    in reply to: recordings not saving #7150
    Jeremy
    Keymaster

    Hi Agnes,

    I don’t think the audio limit on Chrome should have an impact on audio recording. Do you know if the issue only arises for participants with Chrome 92?

    The message suggests that there is a problem with uploading the recordings, not necessarily with creating them. Did you change anything on the amazon side?

    If possible, please share your project’s demonstration link with me, here or at support@pcibex.net

    Jeremy

    in reply to: Using pick with several conditions #7146
    Jeremy
    Keymaster

    Hi Josiane,

    At this point it would be much easier for me to help you if you could share your project’s demonstration link (Share > Copy Demonstration Link, in the right menu of your project’s page)

    I am not sure what your table looks like, but I have created a variant of the project I shared above, this time with a base of 18 items and using your code (just slightly edited). You can directly test both groups here:

    There is one fatal error in your case-0 code: you reference an array named conditions where it should be conditions_families (on the line where you set families)

    Let me know whether you were able to fix the situation

    Jeremy

    in reply to: Using pick with several conditions #7144
    Jeremy
    Keymaster

    Hi Josiane,

    I think your design will require some tailored coding. What I can propose is you use a single CSV table and rename your column Experiment as Group, so that PennController knows to pick only the Exp1 or Exp2 rows at a time

    Now doing just that would include all the items of one experiment, but you actually have specific requirements for your sequence of trials. What I suggest is you label your trials Family+Condition: we can create a javascript array containing all the labels ("1normal","1weird","1opposite","2normal", etc.) shuffle the array, and pick the first N entries from this array (with an additional trick to handle conditions distribution)

    On top of that, if I understood your description correctly, you have different numbers of conditions in exp1 vs exp2, and also an essentially different sequence of trials, since for exp1 you want the test trials in a randomized order followed by all the filler trials in a randomized order, whereas for exp2 you want a subset of the trial and of the filler items intertwined in a randomized order. In order to handle the two differently, we will need to look up the value of the internal counter (__counter_value_from_server__) and execute distinct pieces of javascript code base on it (using a switch statement)

    Here is an example, assuming a simpler table with just 8 non-filler items, 2 conditions in exp1 (“a” and “b”) and 4 in exp2 (“a,” “b,” “c” and “d”). For exp1, we show 4 trials (2 repetitions of each condition) followed by all the filler items; for exp2 we show 8 trials (again, 2 repetitions of each condition) intertwined with 8 filler items (50/50 in condition “a”/”b”)

    
    switch (__counter_value_from_server__ % 2) {
        case 0:
            n_families_total = 8;
            n_families_keep = 4;
            conditions = ["a","b"];
            families = [...new Array(n_families_total)].map((v,i)=>conditions.map(c=>String(i+1)+c));
            fisherYates(families);
            Sequence(
                randomize(anyOf(...families.slice(0,n_families_keep).map((v,i)=>v[i%conditions.length]))),
                randomize(startsWith("filler"))
            );
            break;
        case 1:
        default:
            n_families_total = 8;
            n_families_keep = 8;
            conditions_families = ["a","b","c","d"];
            families = [...new Array(n_families_total)].map((v,i)=>conditions_families.map(c=>String(i+1)+c));
            fisherYates(families);
            n_fillers_total = 16;
            conditions_fillers = ["a","b"];
            fillers = [...new Array(n_fillers_total)].map((v,i)=>conditions_fillers.map(c=>"filler"+String(i+1)+c));
            Sequence(
                rshuffle(
                    anyOf(...families.slice(0,n_families_keep).map((v,i)=>v[i%conditions_families.length]).flat()),
                    anyOf(...fillers.slice(0,n_families_keep).map((v,i)=>v[i%conditions_fillers.length]).flat())
                )
            );
            break;
    }
    
    Template( row=>
        newTrial( row.Family + row.Condition ,
            newText(row.Item + row.Group).print(),
            newButton(row.Evaluation).print().wait()
        )
    )
    

    Here’s an edited excerpt of the table I’m using:

    
    
    Family,Condition,Item,Evaluation,Group
    1,a,item 1a,eval 1a,exp1
    1,b,item 1b,eval 1b,exp1
    2,a,item 2a,eval 2a,exp1
    2,b,item 2b,eval 2b,exp1
    filler1,a,filler item 1a,filler eval 1a,exp1
    filler1,b,filler item 1b,filler eval 1b,exp1
    filler2,a,filler item 2a,filler eval 2a,exp1
    filler2,b,filler item 2b,filler eval 2b,exp1
    1,a,item 1a,eval 1a,exp2
    1,b,item 1b,eval 1b,exp2
    1,c,item 1c,eval 1c,exp2
    1,d,item 1d,eval 1d,exp2
    2,a,item 2a,eval 2a,exp2
    2,b,item 2b,eval 2b,exp2
    2,c,item 2c,eval 2c,exp2
    2,d,item 2d,eval 2d,exp2
    filler1,a,filler item 1a,filler eval 1a,exp2
    filler1,b,filler item 1b,filler eval 1b,exp2
    filler2,a,filler item 2a,filler eval 2a,exp2
    filler2,b,filler item 2b,filler eval 2b,exp2
    

    And here is a link to a demo project so you can see it live and inspect its code: https://farm.pcibex.net/r/tmXZTo/

    Let me know if you have questions

    Jeremy

Viewing 15 posts - 766 through 780 (of 1,522 total)