Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterAh, 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
Jeremy
KeymasterI 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
Jeremy
KeymasterHello 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
Jeremy
KeymasterHello 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 bulletsOnce you have that, it’s a matter of playing with the CSS rules, in particular
transform
andtransform-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
Jeremy
KeymasterMaybe 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
Jeremy
KeymasterHi 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
Jeremy
KeymasterHello Diana,
You could use a Function element to detect
seeking
events on the video and perform operations accordinglyHere is a suggestion — I also reworked the
print
logic, because the visual arrangement appeared off for meTemplate ("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
Jeremy
KeymasterYes, sure, try it and keep it like this if you like it!
Jeremy
Jeremy
KeymasterDear 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
Jeremy
KeymasterHi 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
Jeremy
KeymasterHi 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 toSERVER_MODE = "cgi"
(so only bullet 5 from the documentation should be relevant). Then your experiment will be accessible athttps://your.domain/path/experiment.html
and automatically served on port 80/443 (depending on whether you’re onhttp
orhttps
)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, typepython server.py
, then I open my browser and visithttp://localhost:3000/experiment.html
and there I can see my experiment runningI have never needed to run
sh mkdist.sh
Let me know whether you were able to run your experiment
Jeremy
Jeremy
KeymasterI 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 asynchronousUploadRecordings
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 singleTemplate
command that will start like this:Template( "L1_all.csv" , row => newTrial( row.Label ,
Jeremy
Jeremy
KeymasterHi 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
Jeremy
KeymasterHi 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:
- Group A: https://farm.pcibex.net/r/CHtOnZ/server.py?withsquare=0
- Group B: https://farm.pcibex.net/r/CHtOnZ/server.py?withsquare=1
There is one fatal error in your case-0 code: you reference an array named
conditions
where it should beconditions_families
(on the line where you setfamilies
)Let me know whether you were able to fix the situation
Jeremy
Jeremy
KeymasterHi 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
asGroup
, so that PennController knows to pick only the Exp1 or Exp2 rows at a timeNow 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 aswitch
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
-
AuthorPosts