PsychScript

A starter pack for html5 experiments

Eoin Travers
Queen's University Belfast

This website is out of date. I will eventually figure out how to redirect from this URL, but in the meantime, please try eointravers.com instead.

etravers01@qub.ac.uk

Why go online?

  • Fast data collection
  • Wider demographics
  • Cheap/Free
    Compare: EPrime

Why stay off?

  • Technical hurdles
  • Less controlled environment
  • Variable hardware

Online data collection

State of the art

Flash

Old reliable?

HTML5

New kid on the block?

HTML5 tools

Ease of use

jsPsych

PsychScript?

Flexibility

Write your own

Anatomy of a
HTML5 Experiment

HTML

Content and structure

CSS

Style and appearance

JavaScript

Interactivity

PHP/SQL

Process and save data
on the server

HTML

              
<h2>Experiment</h2>
<!-- This section is where all the stimuli will be presented during the experiment.
In other words, this is where the action happens. -->
<div id='experiment'>
  <p id = 'textAboveProbe'>Is this a word?</p>
  <div id = 'probeHolder'>
    <p id = 'probeText'>
      <!-- Arguments passed to `show_text()` will show up here,
      or wherever the id "probeText" is used.-->
    </p>
  </div>
  <p id = 'textBelowProbe'>
    <!-- This area can be used for other text-->
  </p>
  <p id = 'responseButtons'>
    <!--The response buttons.
    You can have as many buttons as you like here, but you might need to
    change the style to make them all fit.-->
    <button id="yesButton" class = "bigButton">Yes</button>
    <button id="noButton"  class = "bigButton">No</button>
  </p>
</div>
              
            

JavaScript - Stimuli

              
var probes = [
	'backgrounds', 'widespread', 'dropping', 'workshop', 'stressed',
	'maintain', 'judgement', 'commands', 'courtyard', 'variance',
	'unfrocked', 'smoothies', 'bootlegged', 'croquettes', 'toothpastes',
	'stovepipe', 'lockstitch', 'passbooks', 'poolroom', 'hunchbacks',
	'boachers', 'unvaunts', 'stootied', 'twinking', 'resursed', 
	'forecorns', 'choremate', 'glaggers', 'dampbeat', 'trooting',
	'gbjrvcpzdm', 'tbjcvh', 'bwrqlfv', 'whbpjnz', 'phvwkgfnjc', 
	'nbrlptw', 'bsrgvtq', 'kwpqcl', 'cqglhwnrjs', 'wtxnbkfz'
	];

codes = [
	'common_word', 'common_word', 'common_word', 'common_word', 'common_word',
	'common_word', 'common_word', 'common_word', 'common_word', 'common_word',
	'rare_word', 'rare_word', 'rare_word', 'rare_word', 'rare_word',
	'rare_word', 'rare_word', 'rare_word', 'rare_word', 'rare_word',
	'pseudo_word', 'pseudo_word', 'pseudo_word', 'pseudo_word', 'pseudo_word',
	'pseudo_word', 'pseudo_word', 'pseudo_word', 'pseudo_word', 'pseudo_word',
	'consonants', 'consonants', 'consonants', 'consonants', 'consonants',
	'consonants', 'consonants', 'consonants', 'consonants', 'consonants'
	];

var buttons = new Array('yes.png', 'no.png', 'start.png', 'next.png');
var button_imgs = preload_images(buttons, '../images/')

// If using error_feedback, you need to define the possible codes, and 
// the correct response for each.
var list_of_codes = new Array('common_word', 'rare_word','pseudo_word', 'consonants');
var correct_responses = new Array(1, 1, 2, 2);
              
            

JavaScript - Logic

              
var debug_mode = true // Set as true to show extra information, false to run experiment normally.
var error_feedback = false // Do we show feedback when participants make a mistake?

var ITI = 500 // Inter Trial Interval - Duration of fixation cross
var number_of_trials = 40

var variables_to_log = new Array('paradigm', 'experiment_start_time', 'subject_nr', 'trial_number', 'stimuli_number',
     'probe', 'code', 'response', 'rt');
var data_address = 'save_data.php' // URL to send results to.

var paradigm = 'click_button';
var experiment_start_time = Date();
// Pick a random number between 1 and 99999999
var subject_nr = Math.floor(Math.random() * 99999999) + 1; 
var trial_number = 0;
var probe
var code
var response
var rt
var stimuli_number

// Generate random list from 0 to 39.
// This is the order in which the stimuli will be presented.
var random_order = generate_random_list(number_of_trials);

if(debug_mode){
    show('hidden_div')
};

function trial_stage0() {
    deactivate_response_buttons(['yesButton', 'noButton']);
    stimuli_number = random_order[trial_number];
    probe = probes[stimuli_number];
    code = codes[stimuli_number];
    show_fixation(ITI, flash=true); //`true` to flash cross, `false` to show it constantly
    setTimeout(function(){trial_stage1()}, ITI);
};

function trial_stage1() {
    start_time = (Date.now()); // Start a timer
    set_text('probeText', probe);
    show('probeText'); // Show the probe text
    activate_response_buttons(['yesButton', 'noButton']);
};
              
            

[result]

Flexible, generic
HTML templates

Flexible, generic functions

            
// Show and tell
show('element_id');
hide('element_id');

// Stimuli
set_text('element_id', 'Display this text');
show_image('element_id', 'path/to/image.png');

// Work with lists
show(['probe_text', 'probe_image', 'something_else']);

// Collect responses
get_response_keys('yn');
deactivate_keyboard_response();
activate_response_buttons(['yesButton', 'noButton']);
deactivate_response_buttons(['yesButton', 'noButton']);
            
          

Chained, modular logic


Immediately

Set stimuli


trial_stage1();


Wait for click

Show start button


button.onclick=function(){trial_stage2()};


Wait preset time

Show fixation cross


setTimeout(function(){trial_stage3()}, 1000)


Wait for response

Show probe


get_response_keys('yn');

Log response

Are we done?

if (trial_number < total_trials){ trial_stage0();}

Advanced methods

HTMl5 Canvas

            
var unicorn = new Icon('Unicorn', (w*.5)-(pic_w*.5), (h*.5 - r - pic_h*.5), 'unicorn.jpg');
var moving_text = new Text('Catch the unicorn!', 2, h*.8);
var draw_me = [unicorn, moving_text]; 
clickables = new Array(unicorn);

unicorn.onclick = function(){
    cancelAnimationFrame(my_animation)
    alert("Got him!")
}

function = start_move() {
    start_time = Date.now()
    i = 1.6
    animate()
}

function animate() {
    my_animation = requestAnimationFrame( animate );
    move();
    if (progress > rotate_count){
        cancelAnimationFrame(my_animation)
        alert("Too slow!");
    }
}

function move() {
    progress = (Date.now() - start_time) / rotation_time
    theta = progress * Math.PI*2
    s = Math.sin(theta)
    c = Math.cos(theta)    
    unicorn.x = w*.5 + r*s - pic_w*.5
    unicorn.y = h*.5 - r*c - pic_h*.5
    moving_text.y =  h*.5 + r*c
    update_screen()
}
            
          

Advanced methods

Mouse-tracking

Advanced methods

Mouse-tracking

The Server

MySQL - Data storage

            
CREATE TABLE `click_response` (
 `ID` varchar(99) NOT NULL AUTO_INCREMENT,
 `server_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `paradigm` varchar(99) NOT NULL,
 `experiment_start_time` varchar(99) NOT NULL,
 `subject_nr` varchar(99) NOT NULL,
 `trial_number` varchar(99) NOT NULL,
 `stimuli_number` varchar(99) NOT NULL,
 `probe` varchar(99) NOT NULL,
 `code` varchar(99) NOT NULL,
 `response` varchar(99) NOT NULL,
 `rt` varchar(99) NOT NULL,
 PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
            
          

phpMyAdmin

PHP - Data handling

<?php
// Recieve data
$paradigm = $_POST['paradigmOut'];
$experiment_start_time = $_POST['experiment_start_timeOut'];
$subject_nr = $_POST['subject_nrOut'];
$trial_number = $_POST['trial_numberOut'];
$stimuli_number = $_POST['stimuli_numberOut'];
$probe = $_POST['probeOut'];
$code = $_POST['codeOut'];
$response = $_POST['responseOut'];
$rt = $_POST['rtOut'];

//Connect to database
$host = "localhost";
$database = "your_database_name"; 
$username = "your_username";
$password = "YOUR_PASSWORD";
    
$con = mysql_connect($host, $username, $password);
mysql_select_db($database, $con) or die( "Unable to select database");

// Insert data
mysql_query("INSERT INTO click_response (paradigm, experiment_start_time, subject_nr, trial_number, stimuli_number, probe, code, response, rt)
VALUES ('$paradigm', '$experiment_start_time', '$subject_nr', '$trial_number', '$stimuli_number', '$probe', '$code', '$response', rt');");

//Disconnect
mysql_close($con);
?>
          

...and a python script to do the work

# Generate PHP script
def generate_php(database, username, password, table_name, variables_to_log):
    out = "<?php\n// Automatically generated save_data.php file\n"
    for v in variables_to_log:
        out += "$%s = $_POST['$%sOut'];\n" % (v, v)
    out += "\n\n"
    out += "$host = 'localhost';\n$database = '%s';\n$username = '%s';\n$password = '%s';\n\n" % (database, username, password)
    out += "$con = mysql_connect($host, $username, $password);\n\
mysql_select_db($database, $con) or die('Unable to select database');\n\n"
    out += "mysql_query('INSERT INTO %s (" % table_name
    for v in variables_to_log[:-1]:
        out += "%s, " % v
    out += variables_to_log[-1]+")\nVALUES ("
    for v in variables_to_log[:-1]:
        out +=  "'$%s', " % v
    out += variables_to_log[-1]+"');\");\n\n"
    out += "mysql_close($con);\n?>"
    return out

# Generate SQL script to create data table
def generate_sql(table_name, variables_to_log):
    out = "CREATE TABLE `%s` (\n`ID` int(99) NOT NULL AUTO_INCREMENT,\n\
`server_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" % table_name
    for v in variables_to_log:
        out += "`%s` varchar(99) NOT NULL,\n" % v
    out += " PRIMARY KEY (`ID`)\n) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1"
    return out

          
            
variables_to_log = ['paradigm', 'experiment_start_time', 'subject_nr', 'trial_number', 'stimuli_number',
     'probe', 'code', 'response', 'rt']
table_name = 'click_response'
database = 'your_database'
username = 'your_username'
password = 'your_password'

print generate_php(database, username, password, table_name, variables_to_log)

print generate_sql(table_name, variables_to_log)
            
          

Mobile

(the final frontier)

Mobile

(the final frontier)

Adobe PhoneGap/Apache Cordova

Conclusion

PsychScript is...

  • (relatively) easy
  • flexible
  • willing to take care of the hard work
  • growing
  • Open Source

Thanks!


github.com/eointravers/PsychScript
eointravers.github.io

etravers01@qub.ac.uk