Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterI think there is a misunderstanding about the identity and interactions of the different elements in PCIbex.
Tables and columns are never randomized: tables are static pieces of data, typically in the form of CSV files, used to generate trials using the Template command.
What ends up randomized are the trials themselves in the running order, on the basis of their labels. You define the running order using the Sequence command and by referring to trial labels inside it, optionally filtering the correspondingly-labeled trials using various functions like the pre-built function randomize, or the custom function randomizeNoMoreThan in this case.
For example, using the table from your message, this bit of code would make sure you don’t have more than two consecutive trials labeled con nor more than two consecutive trials labeled inc (I had to extract those labels from your trialtype column, since each of the values it contains are already unique within each group):
Sequence( randomizeNoMoreThan(anyOf("con","inc"),2) ) Template( row => // keep only 'inc' or 'con' as the label newTrial( row.trialtype.replace(/^.*(inc|con).*$/,"$1") , newText(row.Flanker).center().print(), newTimer(100).start().wait(), getText(row.Flanker).remove() , newText(row.Stim).center().print(), newKey("FJ").log().wait() ) .log( "type" , row.trialtype ) .log( "group" , row.Group ) .log( "flanker" , row.Flanker ) .log( "sentence" , row.Stim ) .log( "CorrectResponse" , row.Response ) )
To be clear, whenever you run the experiment, you will only see 8 trials, either all from the rows where Group is A, or all from the rows where Group is B. Three of those 8 trials are labeled “inc” and the other five are labeled “con,” based on trialtype. The trial generated from the row where trialtype is “filler” will not be run because its label is neither “inc” nor “con” (and because we used anyOf("con","inc"))
If you only want to prevent more than two consecutive trials generated from rows with the exact same value in trialstype then your table already gives you that because, as I said earlier, each value in that column is unique within its Group. If you want to prevent series of three-or-more trials generated from rows whose trialstype value contains the same AM/UA prefix and/or the same inc/con affix, then we’ll need to modify the randomizeNoMoreThan function, because right now it only tests for exact matches between trials’ labels
Hopefully my message made some points clearer, but please ask any questions you still have
Jeremy
Jeremy
KeymasterHi,
I think it’s a bug introduced in PennController 1.9. Here’s a workaround:
newTrial( newScale("myScale", "not at all", "<span style='visibility: hidden;'>1</span>", "not so much", "<span style='visibility: hidden;'>3</span>", "somewhat", "<span style='visibility: hidden;'>5</span>", "completely" ) .print() .labelsPosition("top") .log() .wait() )
Jeremy
Jeremy
KeymasterHi,
You need to upload a file named PennController.css to your project’s Aesthetics folder with the following:
.PennController { max-width: 40em; left: 50vw !important; transform: translateX(-50%); }
Adapt the value of max-width to your preferences
Jeremy
Jeremy
KeymasterHi Grace,
First, I want to make sure that we’re using Group the same way: in PCIbex, you can name a column either Group or List and the effect will be that, for any given participant, they will only see trials generated from the table that have a given value in that column. As you can see in the tutorial here for example, some participants will only see trials generated from the A rows, and the other participants will only see trials generated from the B rows. I think that, in your case, you are defining groups within participants, and you are not referring to that notion of group, is that correct?
If I understand you correctly, your trials are characterized by three factors: i. A vs B vs C, ii. ambiguous vs unambiguous, iii. congruent vs incongruent. However, you seem to only use the first factor to label your trials. I can’t make sense of the number before A/B/C, does it correspond to the trial’s index?
The way randomizeNoMoreThan works, you pass a set of trials as its first argument (eg. using anyOf, like in your code) and you pass a number N as a second argument. It will make sure that the output series of trials contains at most N consecutive trials that share the exact same label. So in your case, it makes sure that you don’t have more than 2 consecutive trials labeled 1A, that you don’t have more than 2 consecutive trials labeled 1B, etc. So if you want to use randomizeNoMoreThan as it is, my guess is you’ll need to revise how you label your trials.
I understand you don’t want two consecutive trials with the exact same crossing of the two last factors (ambiguity and congurency) but I’m not sure how exactly the group factor interacts with them, so I’m not sure how you want the trials resulting from further crossing the two factors with group to be presented to each participant. If you could clarify that to me, we could work on a better adapted version of the randomizeNoMoreThan function
Jeremy
Jeremy
KeymasterHi,
Starting with PennController 1.9, the commands .left, .center and .right align the element itself on the page(/its container) rather than its content.
If you need to center the content of a Text element, use .css("text-align","center")
Jeremy
Jeremy
KeymasterHi,
I just noticed that your username was on the exceed-quota list. I took it off, since you’re currently using less than 3MB of storage space out of the 64MB quota. You probably exceeded it at some point, which would explain why your account ended up on the list
You should be able to upload files again, let me know
Jeremy
Jeremy
KeymasterYour username did appear on the list, but since you’re now only using about 1.5MB of space, I took it off the list. You should now be able to edit your experiments again
Jeremy
Jeremy
KeymasterHi,
I’m sorry you are experiencing inconvenience. Could you let me know what your username is on the PCIbex Farm so I can look up whether your account ended up on the quota-exceeding list (and take it off if it did)?
Jeremy
Jeremy
KeymasterHi Kimi,
I didn’t realize you were recording audio responses. The log command on the MediaRecorder element will report when the recording started, so you’ll be able to compare that timestamp to the timestamps reported by your Audio element’s log command, and then manually measure when your participants start speaking in each of your collected recording files.
Say your Audio element starts playback at timestamp 1111 and your MediaRecorder starts recording at timestamp 1234 (the timestamps in the results files are in fact much longer, but it doesn’t matter), and by analyzing the recorded sample you find a 250ms silence before your participant starts speaking. This means that your participant gave their audio response 1234-1111+250 = 373ms after the onset of your Audio stimulus.
Let me know if you have any questions
Jeremy
Jeremy
KeymasterI never followed up on this issue here. The problem was as follows:
- The cumulative function accepts a string whose chunks are separated by the wildcard (*) character
- Each of those wildcard-separated chunks can itself contain space characters, which are not chunk separators
- Before a chunk is revealed, any inner space character (ie. non-separator) is masked as an underscore (_) character, like any other character from the chunk
- Pre-reveal, if the full sentence is too long, it will be split across multiple lines where the wildcards (displayed as space characters) appear in the string passed to cumulative
- Once a chunk is revealed, any _ corresponding to an inner space character is now replaced with a space character. As a result, inner and outer space characters are no longer distinguishable and the browser can decide to revise where it inserts line breaks and split the sentence at inner space characters instead of outer space characters, effectively “breaking chunks” visually
The solution was to replace this line from the cumulative function:
getText(textName).text(blanks.map((w,n)=>(n<=i?words[n]:w)).join(' ')) ]);
with this:
getText(textName).text(blanks.map((w,n)=>(n<=i?words[n]:w).replace(/\s/," ")).join(' ')) ]);
Jeremy
Jeremy
KeymasterHi Kimi,
When you use the .log command on an Audio element, it will report a line in the results file for when the audio starts and stops playing. When you use the .log command on an Key element, it will report a line in the results file for when (one of) the key(s) was pressed. You can then compare the different timestamps to determine how fast your participants were to press a key after the audio started or stopped playing
Timing accuracy will vary depending on a range of factors for each of your participants, but it’s usually rather fine-grained, with margins of just tens of milliseconds (although accuracy will drop the more a participant’s browser lags)
I’m not sure what you have in mind in terms of synchronizing things, but if you need to invite your participant to press a key in the middle of the audio, you can do something like this:
newAudio("myAudio.mp3").log().play() , newTimer(1000).start().wait() // wait 1s , newText("Now press F or J").print() , newKey("FJ").log().wait() , getAudio("myAudio.mp3").wait("first")
If your wait time is not constant, and assuming you are generating your trials in a template from a CSV table, you can make it so each row has, in addition to a column containing an audio filename, another column indicating how many milliseconds to wait, and refer to that column in place of 1000 in the sample code above
Let me know if you have any questions
Jeremy
Jeremy
KeymasterHi,
What file(s) are you trying to upload (png, wav, etc.), and how? Do you click on the folder’s + button, or do you sync a git repo?
Jeremy
Jeremy
KeymasterThat error message is returned when move_uploaded_file from line 37 in the PHP script fails. Did you create a subfolder named (exactly) uploads next to your .php file, and did you give it the right permissions (eg. chown -R www-data uploads/ and chmod -R 755 uploads/)?
EDIT: actually you already said the chmods are 750, so maybe just make sure the owner of uploads is properly set (I presume it should be www-data, since you’re running gcloud). If this still doesn’t work, make sure you either have no open_basedir restriction at all, or otherwise that open_basedir gives your PHP script permission to write in the uploads folder
Jeremy
Jeremy
KeymasterHi,
Are you referring to the Ajax error? There can be multiple reasons why you see this error. You want to make sure you have the PHP script from this page uploaded on your webserver, that it has execution permissions, and that you have a secure domain (ie. you can access the PHP script at a URL starting with https)
Jeremy
Jeremy
KeymasterHi,
There could be multiple reasons why your setup is not working as expected. The first thing you want to try is access your PHP script yourself by entering its URL in your browser. If you can’t even see the page, then the problem doesn’t come from the script’s folder-creation or file-writing operations, but from the PHP script’s execution itself.
As far as permissions are concerned, one option is to set the directory containing your PHP to 0755 (basically read+execute across the board) although there are more secure options. Here’s a detailed post about configuring things to give write permission to a PHP script: https://unix.stackexchange.com/a/174114
Feel free to send me the URL to your php script at support@pcibex.net if you want me to give a look, although there won’t be much I can do on my end
Jeremy
-
AuthorPosts