# Experiment Designer various tips

## Contents

- Environment variables
- Error messages
- Experiment logging
  - Console logs
  - File logs
- Getting user input
  - Non-blocking input
  - Blocking input
  - Input that ends the current routine
- Variable routine duration
- Factorial design
- Scripting
- Temporarily disabling a component
- Grouping timelines of components

## Environment variables

Here is the list of environment variables defining usual system path which are usable everywhere a file name is required:

$EXPYVRROOT$ | Path to the installation directory of ExpyVR (where the expyvr.bat is)
$EXPDIR$ | Path to the directory containing the experiment file (where the .ext.xml file is)
$INSTDIR$ | Path to the directory containing the instance file (where the .inst.xml file is)
$USERPROFILE$ | Path to the home directory of the current user.

Back to contents

## Error messages

When you execute an experiment instance, a new python interpreter is lanched and the program crashes in case of error, just displaying a dialog box which looks like this: Hehe ! the filename is wrong! You now know how to solve the problem...

To know more about the error, you shall read the error logs which are saved in a file named expyvrerror.log in the folder log under the execution directory of ExpyVR (where expyvr.bat is).

Example of expyvrerror.log:

    Time: 30 Jan 2011 16:05:06:
    Traceback (most recent call last):
      File "runexperiment.py", line 61, in <module>
        cont.loadExperiment(arg)
      File "C:\Users\nono\Development\expyvr\src\controller\..\..\src\controller\maincontrol.py", line 48, in loadExperiment
        self._loadExpInstance(filename)
      File "C:\Users\nono\Development\expyvr\src\controller\..\..\src\controller\maincontrol.py", line 172, in _loadExpInstance
        self._loadExpOverview(os.path.join(filepath, expNode.attrib['fileName']))
      File "C:\Users\nono\Development\expyvr\src\controller\..\..\src\controller\maincontrol.py", line 201, in _loadExpOverview
        self._loadComponents(tree)      # Load Components
      File "C:\Users\nono\Development\expyvr\src\controller\..\..\src\controller\maincontrol.py", line 277, in _loadComponents
        self._importModule(comp)
      File "C:\Users\nono\Development\expyvr\src\controller\..\..\src\controller\maincontrol.py", line 352, in _importModule
        curComp = mymodule.ModuleMain(self, initConfig=comp.initParams, runConfigs=comp.params)
      File "C:\Users\nono\Development\expyvr\src\controller\..\..\src\billboard\billboardImage.py", line 60, in __init__
        self.pictures[imageName] = image.load(imageName)
      File "C:\Users\nono\Development\python26\lib\site-packages\pyglet\image\__init__.py", line 176, in load
        file = open(filename, 'rb')
    IOError: [Errno 2] No such file or directory: 'C:\\Users\\nono\\Development\\selfportrait2.png'

In case the last line is not sufficient to help you solve the problem, send the full backtrace to the author of the incriminated module.

Back to contents

## Experiment logging

When running an experiment, everything the program does can be logged and used a-posteriori for debugging or analysis. There are two places where the logs are printed out: in the console and/or in a file. You can decide the log type in the logger settings:

The log type can be console (only), file (only) or console and file. Default is console only.

The saving path will by default point to the log directory under the execution directory of Experiment Designer thanks to the environment variable $EXPYVRROOT$, but you can put the path you want (e.g. "$EXPDIR$", "C:/mylogs", or "$USERPROFILE$/experiment/logs"). The given directory is automatically created if not already present.

Back to contents

### Console logs

The console window allows to see logs as they are printed out. This is useful to verify that a script is working. To improve the presentation of the text, customise it by choosing Properties in the context menu of the window title bar (right click). This allows to choose a smaller and nicer font, to change the layout to have larger width and height (number or characters horizontally and vertically, we advise 120 * 3000), or to setup a colorful display.

Back to contents

### File logs

The log file created is named in the following way : <DATE><TIME>_<EXPERIMENTNAME>_log.csv. It can be openned as text and as spreadsheet (coma separated values). By default, the experiment name is 'expyvr'.

Here is an example:

componentType | componentName | absoluteTime | relativeTime | logData
--- | --- | --- | --- | ---
mainDisplay | mainDisplay | 0.2217 | -1 | Added window win 1
mainControl | mainControl | 0.2227 | -1 | Read and parsed experiment
mainControl | mainControl | 0.2235 | -1 | Instantiated logger + Display
mainLogger | mainLogger | 0.2403 | 0 | Logger Started
mainControl | mainControl | 0.2411 | 0.0008 | Starting Flow Item: Routine
mainControl | mainControl | 0.2416 | 0.0014 | Scheduling Routine: Routine
mainControl | mainControl | 0.2421 | 0.0019 | Immediately activating 'hudText:myText' in condition 'default' from 0.0 to 1.0
hudText | myText | 0.2426 | 0.0023 | start with config: {'pause_on_start': False, 'bold': False, 'color': (255, 0, 0, 255), 'text': 'hello', 'withFrame': True, 'posX': 50.0, 'italic': False, 'posY': 50.0, 'font': 'arial', 'size': 26}
mainControl | mainControl | 0.2518 | 0.0115 | Immediately displaying 'hudText:myText' in condition 'default' from 0.0 to 1.0
hudText | hudText | 1.2644 | 1.0242 | stopping
mainControl | mainControl | 1.2647 | 1.0245 | Experiment finished
mainControl | mainControl | 1.2656 | 1.0254 | Cleaning up all instantiated modules
mainLogger | mainLogger | 1.2664 | 1.0262 | Experiment started Mon, 17 Oct 2011 22:42:52 and ended Mon, 17 Oct 2011 22:42:54
mainLogger | mainLogger | 1.2672 | 1.0269 | Total experiment duration: 0:00:01.026153, unpaused experiment duration: 0:00:01.026912
mainLogger | mainLogger | 1.2685 | 1.0283 | Saving logs, number of data entries: 15

Here is how to read this log:

- component type: gives the type of object which wrote this line of log. mainDisplay, mainControl and mainLogger are system objects. In the example, only hudText is a user created module.
- component name: identifies the object; e.g. we can see that there is only one hudText called mytext
- absolute time: the time counting from the begining of the execution of the experiment.
- relative time: the timing which we want to know: when events happen from the experiment start. Negative values mean this even precieds the start of experiment (i.e. all initialisations).

The lines of the log which are of interrest for an experimenter are between "Starting" and "Experiment finished". Typically, one can check with precision the duration of a stimulus; in the above example, we can see that the text "hello" was shown for 1.0127 second (1.0242 - 0.0115). The apparent imprecision is due to the display refresh rate ; at 60Hz, the minimum time to display is 0.0166 s.

EXCEL tip ; to automatically load CSV files with MS Excel (i.e. double clic on a file to import it with comas as separator), you shall setup the default List Separator to be a coma ',' in the custom formatting pannel of your Regional and Language Options from MS control pannel (see MS documentation).

Back to contents

## Getting user input

A typical task in an experiment is to get user's input after a stimuli. As of today, only two components allow to get user input from a button press; keyboard and joystick modules (but the principle would remain the same for future input modules). Here is how it works for the keyboard.

Creating a keyboard component or editting its properties shows the following dialog: The keys field shall contain the list of keys to listen to in the form or space separated upper-case labels of keys (the full list of codes can be found in pyglet documentation). Here are some example of valid keys:

Special keys |
    ENTER SPACE TAB UP DOWN RIGHT LEFT PAGEUP BEGIN
Alphabet keys |
    A B C ...
Numbers keys |
    _0 _1 _2 ...
Numpad keys |
    NUM_0 NUM_1 ...
Function keys |
    F1 F2...

The keyboard module will be listenning to the keys in its active configuration during the activation time (defined in your routine). When one of the keys specified is pressed or released, the event is logged in the experiment general log (cf above).

For convinience, these events are also logged independently in a separate csv file if the logToCSV checkbox is active. In this case, a CSV file is created in the log save path and named <DATE><TIME>_<COMPONENTNAME>.csv

CSV log contains the following columns:

- the time of component start (may concern multiple key presses)
- the name of the current routine
- the name of the configuration of the component
- the label of the key pressed
- the reaction time of key press (time of key press - time of component start)
- the duration of key press (time of key release - time of key press)

Back to contents

### Non-blocking input

If you want to let the user press a key at any time with no effect on the execution of the experiment, you just have to set an activation time as long as you need (e.g. in parallel of another module) and make sure nothing is pausing the experiment.

For example, the keyboard module can record several key press during the 20 seconds of display of a 3D scene: If the user do NOT provide any answer in the given time, the CSV log will still contain an informative line:

    0.1062,routine,default,,-1,-1

If the user press a key but do not release before the end of the key press duration will be -1.

    0.0283,routine,default,RETURN,2.2296,-1

Back to contents

### Blocking input

If you want to ask a question and suspend the execution of the experiment until the user gives an answer on the keyboard, you can do it in this way:

- Create a text module showing your question (and suggesting which keys to press for answer) and make sure the option pause on start is checked in its properties.
- Create a keyboard module (with the corresponding answer keys) and make sure the option unpause is checked.
- Add them in your routine for a very short duration (e.g. 0.1s):

With this solution, the CSV file created by the keyboard module (if logToCsv is on) still contains the experiment time of when the questions appeared (1st column), the response time (5th column) and the label of the answer key. Here is an example with a "L" or "R" keyboard module repeated 4 times in a loop:

    0.0430,routine,default,L,3.0624,0.1001
    0.2748,routine,default,R,0.4025,0.0835
    0.3896,routine,default,R,0.3859,0.1002
    0.5052,routine,default,L,0.2014,0.1002

NB: the experiment time is suspended during the pause; therefore, the execution time (first column) does not account for the several seconds of user response time (5th column). If you don't want this, use an input that ends the current routine.

Back to contents

### Input that ends the current routine

Check the endRoutine option in a keyboard or joystick component to end a routine when a key is pressed. The current routine is immediately finished and the next routine is started regardless of what else was scheduled to happen in this routine.
In the above example, the user has 5 s to answer, but the program will immediately switch to the next routine in the flow once the key is released (CSV logs are normal). Here is what the execution log would say:

    --inputKeyboard:keyboard--; 0.0322; start with config: {'keys': 'ENTER', 'endRoutine': True, 'unpause': False}
    --inputKeyboard:keyboard--; 1.1869; Key RETURN pressed
    --inputKeyboard:keyboard--; 2.0715; Key RETURN released
    --mainControl:mainControl--; 2.0719; Explicit end of current routine called
    --hudText:hudText--; 2.0728; stopping
    --inputKeyboard:keyboard--; 2.0735; stopping

Back to contents

## Infinite routine duration

Sometimes it is not possible to know in advance how long every routine will have to last. We have already seen that it is possible to end a routine by user input. But there is still a fixed maximum length of the routine: the routine always ends once the last component is disactivated and not shown anymore. This screenshot shows how the duration of the keyboard component was set to -1 to get an infinite duration. The infinite duration is shown as the blue (or red) bar going over the scale all the way to the border.

Of course if we have a component with an infinite duration, we need to add a way to terminate that routine. Otherwise the experiment would never finish. We can do that by using an input module and ending the routine on a key press. Another way is to use a script that checks for some condition and ends the routine with the following call:

    self.controller.endCurrentRoutine()

Back to contents

## Factorial design

An experiment often has a factorial design which involve two or more variables taking two or more values. To do this, you can use the factorial loop. If you for example have two colours (red and green) and two directions (left and right) you enter these value in the firstSet and the secondSet of the factorial loop: Then, the routines under that loop will automatically create the necessary conditions: left-red, right-red, left-green, right-green.

Finally, the components in the routines in the loop should define different configurations for the different conditions as needed.

Eventually, you want to make sure that the randomisation of conditions is distributed uniformly among your subjects. You can use the Generate Many Instances functionality in the Experiment menu. This is the dialog that is shown: After choosing the loops which should be used for the shuffling (you may not want to generate all the combinations of all loops), you will be required to choose a directory where to save the multiple files. You can then use the numbered instance files for your subjects.

It is also possible to manually create custom combination of conditions by modifying an instance xml file generated and by re-ordering the conditions in the xml text to match your needs. Here is an .inst.xml file of a simple experiment:

    <ExpyVR version="0.003" encoding="utf-8" type="instance">
      <Experiment fileName="experiment.exp.xml"/>
      <ExperimentFlow>
        <Routine routineName="start" condition="default"/>
        <Routine routineName="display" condition="left-green"/>
        <Routine routineName="display" condition="right-red"/>
        <Routine routineName="display" condition="left-red"/>
        <Routine routineName="display" condition="right-green"/>
        <Routine routineName="thanks" condition="default"/>
      </ExperimentFlow>
    </ExpyVR>

To generate different instance files, move the display routines lines so that the order of conditions follows the orders you want, and save the file under a different name.

Back to contents

## Scripting

### Using python scripts in an experiment

Simple python scripting is possible thanks to the PythonScript component. It allows to provide an init script (executed once at run time) and an update script (executed every frame).
There are two ways to provide code; either direclty in the properties of the module (see bellow), either by providing a filename:
In the example above, the script will call the python file init.py (located in the directory containing the experiment file) once at the instanciation of the component, and will repeatitively call the script update.py at each frame. They should be normal python text files (statement groupping by indentation, etc.).
Here are some speficic guidelines for programming in an ExpyVR script component:

- Local variables are possible, but they are valid only during one execution (e.g. not shared between two runs of the update code).
- Component variables (named with prefix 'self.') shall be created and initialized in the init code and are afterward valid during the whole duration of the routine (e.g. between execution of update code).
- Delta time is accessible to the update script as the local variable dt (dt = time in second since last update)
- New classes can be declared in the init code.
- Importing modules does not work , but many python standard modules are imported by default; os, csv, datetime, re, serial, socket, numpy (as np), and all pyglet.gl opengl functions.

### Connecting components

What really makes the script component seful is its ability to read and modify the attributes of other modules.

To know which attributes are accessible for scripting, open the properties dialog of a component (click and select edit) and check which attributes are displayed in bold in the list:

As you can see, the attributes scale, x, y, depth and angle of a billboardImage component (left) are accessible and modifyable. The joystick attribute of the joystick module (right) is displayed separately and without a value, which means it is a read-only attribute.

Here is an example showing how to create a script which moves the billboard image with the joystick:

- Create a billboardImage component and call it image and provide it with a valid image
- Create a Joystick component called joystick
- Create a PythonScript component called script.
- Ensure the script component is initialised after the two others by clicking on the component and selecting move to bottom from the context menu.
- In the initCode for the script, create module variables (starting with "self.") which refer to the other modules by using the list of modules of the controller:

      self.img = self.controller.gModuleList['image']
      self.jtk = self.controller.gModuleList['joystick']

  NB: Mutliple instruction can be separated by a semicolon ";" when entering them on a single line.
- In the updateCode of the script, link the attribute of the joystick module to the attribute of the image module:

      self.img.x += self.jtk.joystick.x * dt

In the line above, the modifiable attribute x of the component referenced by self.img (BillboardImage) is incremented by the value of the x axis of the attribute joystick (DirectX object) of the Joystick component referenced by self.jtk.

Back to contents

## Temporarily disabling a component

For practical reasons, it is sometimes useful to test an experiment without activating one of its components, but you don't want to remove it completely as this would require to re-insert it every where afterwards.

You can disable and enable a component by selecting the option enable/disable in the context menu of the component. It will then appear with a red icon () showing that it is disabled. Repeating the operation re-enables the component. After disabling the component, it appears like this: NB: you have to save the experiment (after disabling a component) before running an instance.

Back to contents

## Grouping timelines of components

The activation and display times of a component in a condition are called timelines. Often the timelines of a given component are the same in all or most of the conditions of a routine. To make it easier to change the timings you can group the timelines and edit them all at once. To add a timeline to the group, simply check the checkbox of the corresponding component: In the above situation the hudText timeline is grouped whereas the keyboard one is not. Note that there is one group per component, not one group for all the components in a routine. If we now add the keyboard component to its group, we can see how the timeline changes (it takes over the timings of the group) and shows a dialog box explaining for which conditions the timeline is now the same: In the timeline properties window it always explains which conditions will be affected: When you remove a timeline from the group, you probably want the timings to be different. That is why when you remove a timeline from the group, the timeline properties for that individual timeline are automatically shown.

Grouping only affects the timings, it has no influence on the config that is used in the different conditions.

Back to contents

Last update: 15.10.2011 | Authors: Tobias Leugger, Bruno Herbelin