PXLab is a software system for the design and execution of psychological experiments. It is based on the Java programming language and PXLab based experiments can be run on every computer system which can run Java programs. This covers Microsoft Windows, Unix and its many derivatives, including Apple Macintosh computers. PXLab experiments are controlled by design files which can be created with any arbitrary text editor and the same design file will run on every operating system. PXLab experiments can also be run as applets under browser control using the same design file which is used for local client applications.
This tutorial tries to introduce the features of PXLab by examples. The tutorial contains applets which may be run within the browser and it contains exercises which you should work on. You will need to download the example files and edit them with a text editor like Windows Notepad.
You will have to install a Java runtime environment and the PXLab runtime package on your system in order to run the examples as local applications after you have edited the design files.
| Three Steps to Run PXLab Applications |
|---|
| 1. Make sure that you have the latest Java software installed. You can check this by going to java.com, the download page for the Java runtime environment (JRE). Select 'Verify Installation' (German: 'Installation überprüfen') from the choices offered on this page and press the button labeled 'Verify Installation' on the subsequent page. If the response tells you that you have the latest Java then goto step 2. If you do not have the latest Java software, then go to the download section of java.com and download the latest Java runtime environment. Use the 'Typical setup' installation option and use 'Verify installation' to check whether the installation was successful. [This step is sufficient to run most of the demo applets contained in this manual and on the demo pages.] |
2. Download the file PXLabRT.zip
and unzip it into a PXLab installation directory of your choice,
c:\pxlab say. You will need a program to unzip the
archive PXLabRT.zip. If you do not have such a program installed then
Windows will offer you to open the zip archive directly. Create your
destination directory c:\pxlab with the file explorer in
this case and copy the archive content into this directory.
|
3. Use your file explorer to find the file named
pxlab.jar in the PXLab installation directory. Double
click on this file and the PXLab ExRun control program starts with its
Open Design File dialog. Go into the subdirectory of your installation
root named pxd and choose a design file (a file with
extension pxd) from this subdirectory and the experiment
will start.
|
Although this tutorial will in some cases refer to Windows features you should keep in mind that the core of PXLab is pure Java code and thus you will be able to run the examples of this tutorial on every system where a Java runtime environment is available.
Every experiment which is run by PXLab is defined in a design
file. This is a text file which can be edited using any text
editor which does not insert text formatting information into the text
file. On a Windows system Notepad is appropriate for this task. Design
files usually have the extension pxd.
Let's start with a very simple example. Suppose we want to measure the binary choice response time to a left/right signal. The signal has two states: left or right and the subject has two response alternatives: the left and the right mouse button. The first version of our experiment will use a single word as a signal. The signal either is 'left' or is 'right'. The signal will be shown on the screen and the subject's task will be to press the corresponding mouse button as fast as possible. Here is a design file which defines such a task:
Design File crt_001.pxd
Experiment() {
Context() {
AssignmentGroup() {
SubjectCode = "pxlab";
TrialFactor = 2;
}
Trial(TextParagraph.Text, TextParagraph.ResponseTime) {
TextParagraph() {
Timer = RESPONSE_TIMER;
}
}
}
Procedure() {
Session() {
Block() {
Trial("left", ?);
Trial("right", ?);
}
}
}
}
Every design file starts with the term Experiment. The name Experiment is followed by a pair of round brackets which itself are followed by an opening brace. The last character of the file must be the corresponding closing brace. The content of this pair of braces is the Experiment section. This is the highest level block of a design file. The label Experiment tells PXLab that this starts the definition of an experiment. The round brackets may contain arguments. These are variables which contain information for PXLab at runtime. We will come back to these later. The braces of the Experiment section contain the definition of the major parts of the experiment. A design file is a collection of structured sections or blocks. The highest level block named 'Experiment' contains up to three subsections: the Context, the Factors, and the Procedure section. These sections will be called 'top-level' blocks or sections.
Experiment() {
Context() {
...
}
Procedure() {
...
}
}
|
In our case the definition of the experiment contains two top level sections of PXLab: the Context section and the Procedure section. The Context section defines all stimulus properties. The major job of the Context section is to define the stimuli for single trials. This is done by binding together stimuli from the PXLab collection of stimuli and setting their parameters.
Within the PXLab system the concept of a 'display' corresponds most closely to what we usually call a 'stimulus'. We will consider a single trial to contain multiple stimuli. As an example there may be an attention signal, a target signal and a feedback message within each single trial. Thus we assume a trial to contain a sequence of stimuli or display objects. The notion of a 'display object' relates to the object orientation of the Java programming language and, when talking about design files, we will use the terms 'stimulus' and 'display' as synonyms. Thus in PXLab 'stimuli' correspond to 'display objects' and a trial will be defined by a sequence of stimuli or a sequence of display objects. Such a sequence is called 'display list' in PXLab. A display list is a sequence of display objects and the definition of a trial display list will completely describe what happens in a trial. In addition to defining the trials, the Context section also contains parameter settings for global parameters of an experiment like a repetition factor for trials. Global parameters are parameters which are not directly related to stimuli but may control other features of an experiment. The global parameter settings are collected together in an AssignmentGroup. In our case the AssignmentGroup defines two parameter values. The first is a default value for the global parameter SubjectCode which is used to identify a single subject. The second assignment is a value for the parameter TrialFactor which defines the number of repetitions of each trial. Note that a global parameter is simply set by an equation statement: We have to write the name of the parameter, the equals sign, and the value to be assigned to the parameter. A Context section may contain more than a single AssignmentGroup subsection.
Experiment() {
Context() {
AssignmentGroup() {
SubjectCode = "pxlab";
TrialFactor = 2;
}
...
}
...
}
|
The most important part of the Context section is the definition of a trial. This starts with the name Trial followed by a list of arguments enclosed in round brackets and then followed by a block which is enclosed in braces. The meaning of Trial arguments will be explained later. The Trial block contains the list of stimulus objects which are shown to the subject during a trial. In our example the list contains only a single display: TextParagraph. This is a display which shows one or more lines of text on the screen.
Experiment() {
Context() {
...
Trial(TextParagraph.Text, TextParagraph.ResponseTime) {
TextParagraph() {
Timer = de.pxlab.pxl.TimerCodes.RESPONSE_TIMER;
}
}
}
...
}
|
Every stimulus or display like TextParagraph has many parameters which may be used to define its properties. In our case this may be the font, the color, the position and many more properties. Here we use only three property parameters of the TextParagraph object: the Timer, the TextParagraph.Text, and the ResponseTime.
The parameter Timer is set to the value RESPONSE_TIMER. This tells PXLab that the time interval for showing the TextParagraph object is stopped by a subject response. Thus the TextParagraph display object will be shown on the screen and then PXLab will wait until it detects a subject response. If this happens, the TextParagraph object will be replaced by the next stimulus object. The parameters TextParagraph.Text, and the ResponseTime are not used within the block defining the properties of TextParagraph, but are used as arguments for the Trial section. This means that the values of these parameters are not fixed but may change its value for every trial. They behave as if they were variables whose values may change for every trial.
This finishes the definition of the stimulus properties in our example. However, We still have not defined the actual text for every trial which will be shown on the screen. This definition is contained in the list of executable trials which is contained in the Procedure section of the design file. The section named Procedure is contained in the Experiment section and defines the actual sequence of sessions, blocks, and trials of an experiment. We will call the Session, Block, and Trial sections contained in the Procedure section 'procedure units' in order to differentiate them from the corresponding definitions in the Context section. In our simple example the Procedure section contains a single Session, which contains a single Block, and this Block contains two Trials. The Trial entries in the Procedure section do not have a block enclosed in braces but do only have a list of argument values. In this case, the arguments are not names of experimental parameters but are values which will be assigned to those experimental parameters whose names are contained in the argument list of the trial definition in the Context section. We will call the list of parameter names in the argument of a display list definition in the Context section an 'argument list' and we will call the list of values in the argument of a proicedure unit a 'argument value list'. The Trial procedure unit definition
Trial("left", ?);
|
The Procedure section of our example contains two Trial
definitions. One for the 'left' and one for the 'right'
stimulus. Since the global experimental parameter TrialFactor is set to 2 in the AssignmentGroup every trial
defined in the Procedure section will be shown 2 times. So our design
file actually defines four trials. Two with the 'left' and two with
the 'right' stimulus. By default the trial sequence will be
randomized. The button below starts the experiment. It will be shown in
a window and you may use the mouse buttons to respond. Make sure that
the mouse pointer is inside the stimulus window when you respond.
When running this 'experiment' you will immediately notice one major problem: there may be no feedback which tells the subject that the response has been accepted. This happens in those cases where the two identical stimuli are shown immediately after one another. In these cases the screen does not change after the response. What is the reason for this somewhat strange behavior? The reason is that our trial contains only a single stimulus object. Thus repeatedly showing the same stimulus in subsequent trials will not change the screen content.
The solution to this problem is to insert a second stimulus object which replaces the TextParagraph object immediately after the response has been detected. We do this by inserting a ClearScreen object after the TextParagraph object and define the Timer parameter of the ClearScreen object to be a CLOCK_TIMER. This is a timer which runs for a fixed duration. We use the parameter Duration to define that the ClearScreen object will be shown for 400 ms.
Design File crt_002.pxd
Experiment() {
Context() {
AssignmentGroup() {
SubjectCode = "pxlab";
TrialFactor = 2;
}
Trial(TextParagraph.Text, TextParagraph.ResponseTime) {
TextParagraph() {
Timer = RESPONSE_TIMER;
}
ClearScreen() {
Timer = CLOCK_TIMER;
Duration = 400;
}
}
}
Procedure() {
Session() {
Block() {
Trial("left", ?);
Trial("right", ?);
}
}
}
}
Now the subject will notice that the response has been accepted by
the short clear screen between successive trials.
The following exercises require you to edit a design file. On Windows you may do this in the following way: Start the Windows Notepad editor. Use your browser to open the above design file crt_002.pxd and copy the file's content from the browser window into the Notepad window. Then edit the design file according to the exercise and use 'Save File as' in Notepad to store the new design file into your design file directory or wherever you like. Note that you should select 'All files' in the Notepad file dialog and use the extension 'pxd' for your design file. If you do not select 'All Files' in the Notepad file dialog then Notepad may add the extension 'txt' to your file name and you might have difficulties to find your file later. Then start the PXLab ExRun application by double clicking on pxlab.jar and open and run your newly created design file.
If there is a syntax error in your design file then PXLab can tell you where the error is. In this case PXLab will open an error dialog box and tell you what type of error had been found and in which line and at which character position the error is.
Our first example still lacks some important features of a real experiment. We will now add some of these properties:
Looking at the Procedure section of our design file we see that there actually are four types of procedure objects: Procedure, Session, Block, and Trial. For every procedure object we can define stimuli to be shown when the respective procedure object starts and when it ends. An exception is the Trial procedure which does not have separate stimuli for its end.
| Unit | When it is presented | How many instances | necessary: yes/no |
| Procedure | program starts | only one instance can exist | no |
| Session | a session starts | multiple instances may exist | no |
| Block | a block starts | multiple instances may exist | no |
| Trial | whithin a block | multiple instances should exist | yes |
| BlockEnd | a block is finished | multiple instances may exist | no |
| SessionEnd | a session is finished | multiple instances may exist | no |
| ProcedureEnd | all sessions have been run | only one instance can exist | no |
The stimuli for the procedure objects are defined in the Context section as we have done for the Trial. Stimuli which are shown in a procedure are called display list in PXLab since they are instances of the class DisplayList. A display list is a sequence of display objects which are instances of class Display. Our design file currently defines only a single display list, the display list for the trial. The display list for our trial contains a sequence of two display objects: TextParagraph and ClearScreen.
Now lets add a display list for the start of a Session procedure which then will be used as an introduction to the subject. This is done by adding a Session definition to the Context section. The Session definition contains a single display object named Instruction. The Instruction is essentially the same as a TextParagraph but has some special features for introductory screens. We need only set a single parameter of the Instruction object, its TextParagraph.Text parameter.
Design File crt_003.pxd
Experiment() {
Context() {
AssignmentGroup() {
SubjectCode = "pxlab";
TrialFactor = 2;
}
Session() {
Instruction() {
Text = ["Welcome to the Experiment",
" ",
"You will see a 'left' and a 'right' signal.",
"Your task is to press the respective mouse button as fast as possible.",
" ",
"Press any key now to start the experiment."];
}
}
Trial(TextParagraph.Text, TextParagraph.ResponseTime) {
TextParagraph() {
Timer = RESPONSE_TIMER;
}
ClearScreen() {
Timer = CLOCK_TIMER;
Duration = 400;
}
}
}
Procedure() {
Session() {
Block() {
Trial("left", ?);
Trial("right", ?);
}
}
}
}
Note that we use an array to set the
parameter TextParagraph.Text to a complete paragraph
of text. Experimental parameter values may be arrays. In this case we
have an array of strings. Every single string is a sequence of text
characters enclosed between double quotes. The array is a sequence of
strings separated by a semicolon and enclosed between square
brackets. We use two strings which contain only a space character to
generate empty lines. The Instruction object breaks
lines into paragraphs of text but observes strings. Thus the start of
a new string will always start a new line.
Now we want to implement feedback for the subject. The first step will be to tell the subject the actual response time achieved. The task then is to show a text on the screen which contains the actual response time. We can do this by showing another TextParagraph object and set its TextParagraph.Text parameter to a string which contains the actual response time.
TextParagraph:Feedback() {
Text = "%Trial.TextParagraph.ResponseTime@i% ms";
Timer = de.pxlab.pxl.TimerCodes.CLOCK_TIMER;
Duration = 1000;
JustInTime = 1;
}
|
This declaration contains several important new features to note:
The last modification we want to make is to check whether the subject's response is correct or not. This requires two changes of our design file: first we have to tell PXLab for every trial what the correct response is, and second we have to modify the feedback such that it reflects the correctness of the response. The first step requires a change of the Trial argument list. We have to enter the code of the response device which has been activated by the subject into the argument list and we have to enter the correct value of this code. The code of the response is stored in parameter ResponseCode of the TextParagraph stimulus object. So we add 'TextParagraph.ResponseCode' to the Trial argument list.
In order to tell PXLab what the correct code is, we have to add another parameter which will contain the correct code. This is not a parameter of the TextParagraph stimulus object. We have to create a new parameter for this and can do this in the AssignmentGroup of our Context section:
AssignmentGroup() {
SubjectCode = "pxlab";
TrialFactor = 2;
new correctCode = 0;
}
|
The attribute 'new' creates a new experimental parameter with the given name. Its value is initialized to 0. We can now add this parameter to the list of Trial arguments and can then set its value to the correct response code for every trial.
Trial(TextParagraph.Text, correctCode, TextParagraph.ResponseCode, TextParagraph.ResponseTime) {
...
|
Remember that Trial arguments are those parameters which can have different values in every trial. This is true for the stimulus text and for the correct response code. These two parameters can be thought of as the operationalization of our independent variable 'orientation'. The parameters for the response code and time also will have different values for every trial since these will store the experimental data.
The Trial definitions in the Procedure section have to be modified too. They now contain four arguments: The stimulus word, the correct response code, the response code data, and the response time data.
Procedure() {
Session() {
Block() {
Trial("left", LEFT_BUTTON, ?, ?);
Trial("right", RIGHT_BUTTON, ?, ?);
}
}
}
|
Finally we have to define what has to be done with the information about the correct response code. We modify our feedback text. If the response code is correct then we present the response time as before. If the response is false, we change the text to 'False !' and do not show the response time.
TextParagraph:Feedback() {
Text = (Trial.TextParagraph.ResponseCode == correctCode)?
"%Trial.TextParagraph.ResponseTime@i% ms": "False !";
Timer = CLOCK_TIMER;
Duration = 1000;
JustInTime = 1;
}
|
This is achived by using a conditional expression to define the feedback text. A conditional expression is an expression which looks like
A? B: C |
If the term A is true then the value of this conditional expression is B. If A is false then the value of the expression is C. Usually the term A in a conditional expression is a logical statement. In our case it is
Trial.TextParagraph.ResponseCode == correctCode |
This term is true if the value of the experimental parameter Trial.TextParagraph.ResponseCode is equal to the value of the experimental parameter correctCode. Thus the term is true if the ResponseCode parameter of the TextParagraph object in the current trial is equal to the current value of correctCode. The parameter correctCode is defined by the experimenter and contains the response code which corresponds to the correct response. The correct response for the 'left' signal is the left mouse button. Its code is LEFT_BUTTON. The correct response for the 'right' signal is the right mouse button and its code is RIGHT_BUTTON.
The term 'B' in our conditional expression is
"%Trial.TextParagraph.ResponseTime@i% ms"
|
This is a text string which contains the current integer value of the parameter Trial.TextParagraph.ResponseTime followed by 'ms'. The mechanism which replaces an experimental parameter name enclosed between two percent signs by its value has been described earlier. The value 'C' in our example is the string
"False !";
|
which only contains the text 'False !'. The result is that the conditional expression contains a string showing the response time in case of a correct response and showing 'False !' in case of false responses.
Remember the Trial definition:
Trial(TextParagraph.Text, correctCode, TextParagraph.ResponseCode, TextParagraph.ResponseTime) {
...
|
The corresponding Trial procedure is
Trial("left", LEFT_BUTTON, ?, ?);
Trial("right", RIGHT_BUTTON, ?, ?);
|
The first Trial assigns "left" to Trial.TextParagraph.Text and LEFT_BUTTON to correctCode. This tells the program that LEFT_BUTTON is the correct response for the 'left' signal.
Also remember that this only works since we have set JustInTime to 1. Only then do we get a valid feedback text since this forces the program to evaluate the conditional expression after the response has been collected.
Design File crt_005.pxd
Experiment() {
Context() {
AssignmentGroup() {
SubjectCode = "pxlab";
TrialFactor = 2;
new correctCode = 0;
}
Session() {
Instruction() {
Text = ["Welcome to the Experiment",
" ",
"You will see a 'left' and a 'right' signal.",
"Your task is to press the respective mouse button as fast as possible.",
" ",
"Press any key now to start the experiment."];
}
}
Trial(TextParagraph.Text, correctCode, TextParagraph.ResponseCode, TextParagraph.ResponseTime) {
TextParagraph() {
Timer = RESPONSE_TIMER;
}
TextParagraph:Feedback() {
Text = (Trial.TextParagraph.ResponseCode == correctCode)?
"%Trial.TextParagraph.ResponseTime@i% ms": "False !";
Timer = CLOCK_TIMER;
Duration = 1000;
JustInTime = 1;
}
ClearScreen() {
Timer = CLOCK_TIMER;
Duration = 400;
}
}
}
Procedure() {
Session() {
Block() {
Trial("left", LEFT_BUTTON, ?, ?);
Trial("right", RIGHT_BUTTON, ?, ?);
}
}
}
}
Start the example program and check the feedback by giving correct
and false responses.
The file crt_006.pxd contains a sample solution to these exercises.
Now that we have set up a first experiment we will see how PXLab defines stimulus objects and their properties. The stimulus objects we have been using so far are
We stick to our choice response example experiment. The next step is to add an attention signal and to replace the verbal direction signal by a graphic direction cue. The attention signal will be a fixation mark and the target signal will be an arrow pointing to the target direction.
The PXLab library contains a fixation mark signal called FixationMark. If you click on the link to FixationMark you will arrive at a page which contains the description of the class FixationMark. The term 'class' here refers to the Java programming concept of a 'class'. PXLab display objects are instances of classes with the same name. A Java class is the Java language concept which is used to define programming constructs and its properties. We need not touch the Java internals here. We only might remember that display objects correspond to Java classes and every single instance of a display or stimulus object is an instance of a Java class. The major feature of Java classes is inheritage: every class is a subclass its superclass and every subclass inherits all properties of its superclass. The superclass of all display objects in PXLab is the class Display. Stimulus parameters which belong to every stimulus are parameters of the class Display and are inherited by all subclasses. Examples for these are the parameters Timer, ResponseTime, or ResponseCode. Other parameters like FixationMark.Size are special for a single class and thus do only exist for this display object.
The class FixationMark is the actual program element which generates the fixation mark and the class description is the most complete description of all properties of FixationMark which you can get. For our purpose here the most important part of this description is the list of experimental parameters contained in the section with the heading 'Field Summary'. We look at the following properties here:
Showing the attention signal should be the first step in a trial. Thus the attention signal must be the first stimulus component to be shown. Its duration should be fixed and it should be replaced by an empty screen. Thus the beginning of the new Trial definition might look like this:
FixationMark() {
Type = FIXATION_CROSS;
Size = 30;
LineWidth = 3;
Timer = CLOCK_TIMER;
Duration = 500;
}
ClearScreen:Attention() {
Timer = CLOCK_TIMER;
Duration = 300;
}
TextParagraph() {
Timer = RESPONSE_TIMER;
}
...
|
We have inserted the FixationMark and a new ClearScreen object in front of the
TextParagraph object which shows the target stimulus. Note that the ClearScreen object got an instance name added: we call it
'ClearScreen:Attention' in order to differentiate it from the already
existing ClearScreen object at the end of the trial. This instance
name modifier is necessary since otherwise the display objects cannot
be accessed from outside of a trial. Also note that we have set both
new objects' Timer parameter to CLOCK_TIMER which results in a fixed duration defined by the
parameter Duration. The file crt_007.pxd contains the complete experiment
including the attention signal.
Our next step now is to replace the verbal signal by an arrow. We use the PXLab display object Arrow. The link to Arrow shows us the special parameters of this class in the section 'Field Summary. Note that the Arrow class actually can show a double arrow. By default, however, the left head of the arrow is set to 0 size such that only a single arrow head is shown by default. Here we only have to change the parameters Arrow.Color, and Arrow.Orientation. Here is the definietion of our arrow element:
...
Arrow() {
Color = lightGray();
Orientation = 0;
Timer = RESPONSE_TIMER;
}
...
|
Note that the Timer value is the same as used before with TextParagraph. The parameter Arrow.Color is set to 'lightGray()' which results in the same color used for all other stimulus elements. We will explain the function 'lightGray()' later. The direction where the arrow points to is defined by the parameter Arrow.Orientation. The value 0 corresponds to a right pointing arrow and the value 180 creates a left pointing arrow.
Replacing TextParagraph by Arrow requires some further changes in our design file. The most obvious ones relate to the argument list of the Trial definition. Since we replace the TextParagraph object as a stimulus element by the Arrow object we also have to replace the respective parameters in the Trial argument list. The new argument list is
Trial(Arrow.Orientation, correctCode, Arrow.ResponseCode, Arrow.ResponseTime) {
...
|
Our new independent variable is 'Arrow.Orientation' and the response data now are contained in Arrow.ResponseCode and Arrow.ResponseTime. The feedback text object also refers to the response code data and has to be changed to use the Arrow parameters.
TextParagraph:Feedback() {
Text = (Trial.Arrow.ResponseCode == correctCode)?
"%Trial.Arrow.ResponseTime@i% ms":
"False !\n%Trial.Arrow.ResponseTime@i% ms";
...
|
And finally we have to modify the Trial elements in the Procedure section in order to use the new values of the independent variable Orientation:
Trial(180, LEFT_BUTTON, ?, ?);
Trial(0, RIGHT_BUTTON, ?, ?);
|
The file crt_008.pxd contains the experiment
with the arrow signal.
Hint: The orientation is defined by the parameter with the full instance name 'Trial.Arrow.Orientation'. You may use a conditional expression as described earlier with the condition 'Trial.Arrow.Orientation == 0' to select the right pointing condition and define the horizontal location depending on whether the orientation parameter is 0 or not.
This seems to be all which has to be done. The file crt_010.pxd contains the picture experiment.
As menioned earlier all stimulus objects in PXLab are display objects and all of them are subclasses of the class Display. The documentation page of this class contains a list of all subclasses which currently exist and which are potential stimulus objects. The manual chapter named Stimulus Elements also contains a list of available stimuli and also contains a list of those experimental parameters which are common to all stimulus objects.
Another way to look at the PXLab collection of stimuli is to start the PXLab Display Editor. Its a relative of the PXLab Vision Demonstrations application and may be started from the button below. See the instructions of the vision demonstrations program on how to use the editor. It lets you select any of the PXLab display objects and shows it on screen. It also shows most of the parameters of every display and lets you mofify the parameter values interactively.
Hints: The target signal may be the display object SimpleDisk and the clear screen period with a random time interval may be ClearScreenRandomTime. Disk color values may be 'darkGray()' and 'white()'.
Trial(white(), ?);
Trial(darkGray(), ?);
|
or if you used coordinates directly it may look like
Trial([ 1.0, 0.309, 0.329], ?);
Trial([90.0, 0.309, 0.329], ?);
|
depending on how you have defined your Trial argument list.
Its time now to look a little bit closer at the PXLab design file syntax. The first aspect which will be treated in this chapter is the distinction between names and values.
PXLab design files have their own grammar rules. The complete description of the PXLab design file grammar is written in the so-called 'Backus-Naur-Form'. We do not need the to study the complete grammar here but we may start with simple assignments of values to experimental parameters. Look at the AssignmentGroup section of example crt_008.pxd:
AssignmentGroup() {
SubjectCode = "pxlab";
TrialFactor = 2;
...
}
|
The first assignment assigns the value 'pxlab' to the parameter named 'SubjectCode'. The parameter name 'SubjectCode' actually is the name (or address) of some storage space in computer memory where the character sequence 'pxlab' will be stored. The double quotes around 'pxlab' tell the program that the character sequence 'pxlab' is to be considered a value and not the name of another parameter. This distinction is necessary since, as we will see later, the PXLab design file syntax allows parameter names not only to apear on the left but also on the right side of an equals sign in an assignment.
The second assignment assigns the integer number 2 to the experimental parameter 'TrialFactor'. This means that the integer value 2 is stored in the storage space named by 'TrialFactor'. In this case it is not necessary to use double quotes around the value '2' because of a special naming rule in the PXLab grammar: a parameter name never has a digit as its first character. This means that a sequence of characters starting with a digit can never be the name of a parameter but always is a value. Actually the syntax of PXLab is a little bit more restrictive: It requires that any single value evaluates to a literal value. And there are essentially only three types of literal values: integer numbers like '1', '255', or '-17', floating point numbers like '1.5' or '3.141592' and string literals like 'pxlab', or '3ABC'. And the rule is that string literals always have to be enclosed between double quotes when used as values in assignments:
SubjectCode = "pxlab";
FileName = "flower.jpg";
Luminance = 34.2;
|
Explicit assignments are not the only place where values are used in a PXLab design file. They are also used in the implicit assignments of a Trial procedure argument value list like that of example crt_011.pxd:
Trial(Picture.FileName, correctCode, Picture.ResponseCode, Picture.ResponseTime) {
...
Trial("8105_lores.jpg", LEFT_BUTTON, ?, ?);
Trial("9163_lores.jpg", LEFT_BUTTON, ?, ?);
Trial("8873_lores.jpg", RIGHT_BUTTON, ?, ?);
Trial("9160_lores.jpg", RIGHT_BUTTON, ?, ?);
|
This is an implicit assignment of the string literal value "8105_lores.jpg" to the parameter 'FileName' of the Picture display object.
This last example also seems to show a violation of the rule that there exist only integer or floating point numbers and literal strings as values. It implicitly assigns the value 'LEFT_BUTTON' to the parameter 'correctCode'. A similar construct has frequently been used to define the Timer parameter:
Timer = CLOCK_TIMER;
|
Actually this is not an exception of the rule. 'CLOCK_TIMER' and 'LEFT_BUTTON' are examples of named class constants. These names are defined in special classes and their essential feature is that they have constant values. Thus the name 'CLOCK_TIMER' is not a variable name which can appear on the left of an equals sign and which can be assigned different values but is a name for a constant value. The purpose of giving a constant value a name is to enhance readability of the code. As an example the integer value of 'CLOCK_TIMER' is 513. It is much easier to remember the name 'CLOCK_TIMER' than to remember the number 513. Furthermore changing the source code of PXLab may change the value of 'CLOCK_TIMER'. Thus a design file using the value 513 would no longer work correctly if the value of 'CLOCK_TIMER' had been changed in the program code. It is a convention that class constant names only use upper case letters in order to indicate their special status. A complete list of all known class constants and their defining classes is contained in the PXLab API documentation.
The above example also shows a special feature of implicit assignments: The character '?' may be used to not assign a value by implicit assignment. This character cannot be used in explicit assignments. Thus the '?'-character can never appear at the right side of an equals sign. If this character is used in an argument value list it may have two purposes:
We have said that values of experimental parameters in PXLab always are integer, floating point or string values. Actually this is incomplete. The truth is that PXLab values always are arrays of these primitive types and that every value simultanously has an integer, a floating point and a string value. Thus it is syntactically correct to simultanously use the following assigments:
FileName = 13;
FileName = 3.141592;
FileName = "rose.jpg";
FileName = ["ann.jpg", "mike.jpg", "jack.jpg", "eve.jpg"];
|
Here is what happens to the internal PXLab values if the various types of assignments are used:
| assigned value | PXLab internal values | ||
| integer | floating point | string | |
| 13 | 13 | 13.0 | "13" |
| 3.141592 | 3 | 3.141592 | "3.141592" |
| 3.8 | 4 | 3.8 | "3.8" |
| "rose.jpg" | 0 | 0.0 | "rose.jpg" |
Remember that these assignments are only syntactically correct. This does not say anything about the semantics. In most cases using '13' as a value for 'FileName' will be syntactically correct but it will be semantic nonsense since no file with such a name will exist. Also, in many cases it won't make sense to assign an array to a parameter since the semantics will be such that only the first entry of the array will be used. An exeption we already have been using several times is the parameter Instruction.Text of the Instruction object:
Instruction() {
Text = ["Welcome to the Experiment",
" ",
"You will see a 'left' and a 'right' signal.",
"Your task is to press the respective mouse button as fast as possible.",
" ",
"Press any key now to start the experiment."];
}
|
Here every element in the array corresponds to a line of text. We see that an array is a list of literal values enclosed in square brackets.
In the previous section we have described assignments of literal values to experimental parameters. The PXLab design language also allows the assignment of expressions to experimental parameters. Expressions may contain literal values, operations, and functions, and may also contain experimental parameters. An example is the conditional expression we have been using for the assignment of feedback text:
Text = Trial.TextParagraph.ResponseCode == correctCode?
"%Trial.TextParagraph.ResponseTime@i% ms": "False !";
|
The expression on the right side of the equals sign contains several experimental parameters and operators. The first subexpression is
Trial.TextParagraph.ResponseCode == correctCode
|
This subexpression is true if the values stored in the parameter named 'Trial.TextParagraph.ResponseCode' is equal to the value stored in the parameter 'correctCode'. PXLab does not have a special logical or boolean type. It uses the convention that a parameter or expression is true if and only if its value is not 0 and a parameter or expression is false if and only if its value is 0.
PXlab can handle most of the usual mathematical operations. Expressions may also contain function calls like in the color example:
Color = lightGray();
|
Here 'lightGray()' is a function which returns a color value which actually is a floating point arrow with three components. The PXLab manual contains a list of all available functions.
Solutions to these exercises are available.
Expressions always have to evaluate to literal constants at runtime. It is important to remember that expressions are not evaluated when read but are only evaluated when the respective parameter is used at runtime.
By default the experimental parameter values of display objects are evaluated immediately before the display list is shown, which contains the respective display object. As an example consider the Trial display list of example crt_008.pxd. It contains the display objects FixationMark, ClearScreen, Arrow, TextParagraph, and another ClearScreen in that sequence. By default all experimental parameters of all of these display objects are evaluated immediately before the Trial starts. The reason is that PXLab builds all screen content of a display list immediately before the list is presented in order to optimize timing speed. This also means that any changes of parameter values after a Trial has started by default are ignored. Thus by default parameter changes due to subject responses are not used in evaluation.
If the value of an experimental parameter changes within a trial and this value should be used in a display object then the respective display object has to make sure that its own parameters are evaluated only immediately before the display object is presented to the subject. Thus is done by the experimental parameter JustInTime. This parameter is a parameter of every display object. It is not a global parameter but every display object hast its own private instance of this parameter. If JustInTimeis set to 1 then the respective display object's parameters are only evaluated immediately before the display object is presented. This feature should be used only if necessary since it may speed down the presentation timing. However, it makes sure that the experimental parameter values also use response information which has only been gathered immediately before.
Thus in our feedback text example the conditional expression is only evaluated when the value of the Text parameter is used. This evaluation has to be done only after the response has been collected since otherwise the parameter 'Trial.TextParagraph.ResponseCode' does not yet contain the actual data value. This is the reason why the parameter JustInTime of the text paragraph display object has to be set to 1. This makes sure that the evaluation of the expression is done only immediately before the text is shown and after the response has been collected.
The feedback example also shows an example of the full instance name of an experimental parameter: 'Trial.TextParagraph.ResponseCode'. Here 'Trial' is the display list which contains the display object 'TextParagraph' and 'ResponseCode' is a parameter of this display object. Experimental parameters come in two groups: Parameters like 'ResponseCode' or 'Timer' which always are parameters of a special display object, and global experimental parameters like 'TrialFactor' or 'SubjectCode' which exist independently of any display object.
new correctCode = 0;
|
This creates a new global parameter which is not associated with any display object and initializes it to 0. This parameter can be used in the same way as any other global parameter.
Experimental parameters are instances of class ExPar and the API documentation page of this class contains a list of all existing global experimental parameters under the heading 'Field Summary' with a description under the heading 'Field Detail'.
In general display object parameters have always be named by their full instance name like 'Trial.TextParagraph.ResponseCode'. There are, however, two exceptions which simplify typing:
Trial(TextParagraph.Text, correctCode, TextParagraph.ResponseCode, TextParagraph.ResponseTime) {
TextParagraph() {
Timer = RESPONSE_TIMER;
}
TextParagraph:Feedback() {
Text = (Trial.TextParagraph.ResponseCode == correctCode)?
"%Trial.TextParagraph.ResponseTime@i% ms": "False !";
Timer = CLOCK_TIMER;
Duration = 1000;
JustInTime = 1;
}
...
}
|
In the argument list of the Trial display list the display list prefix 'Trial' for 'TextParagraph.ResponseCode' is dropped. This is an application of the previously stated rule: The Trial display list contains the TextParagraph display object and thus the prefix for the display list in the argument list can be left out. Note, however, that the full instance name of the parameter 'Trial.TextParagraph.ResponseCode' has to be given in the feedback text expression.
Trial(TextParagraph.Text, correctCode, TextParagraph.ResponseCode, TextParagraph.ResponseTime) {
TextParagraph() {
Text = "left";
...
}
...
}
|
then the parameter name 'Trial.TextParagraph.Text' can be written as 'Text' within any assignment inside of the definition of the TextParagraph object itself. Note that this only holds for the left side of the assignment. The short hand notation is not allowed on the right side of the equals sign.
We previously have stated that new global experimental parameter names can be arbitrary names. This has to be qualified.
The examples in this section assume that there is only a single instance of the display list and the display object used. We have seen earlier that a display list can contain more than a single instance of a display object like TextParagraph. In this case the display object must get an instance modifier which can be any arbitrary name like 'Feedback' to get 'TextParagraph:Feedback'. The instance modifier both for display objects and display lists is separated by a ':'-sign from the class name. Thus if a design file contains multiple instances of Trial definitions it may add instance modifiers like 'Trial:Study' and 'Trial:Test' in order to identify the two different types of trials. The full instance name of an experimental display parameter may get rather large in this case: 'Trial:Study.TextParagraph:Feedback.Text' would in this case be the full instance name of the parameter 'Text' of display object instance 'TextParagraph:Feedback' in display list 'Trial:Study'. Instance modifiers for display lists and objects may be dropped if the names remain unique.
FontSize = idiv(screenHeight(), 28); |
Figure out what this means and define the parameter TextParagraph.FontSize such that there fit at least 32 lines of text on the screen. Hint: see the respective manual page on the meaning of the function idiv().
Experiments sometimes exist in slightly different variants with most of the declarations being identical. In those cases it is frequently useful to to put some of the declarations into an external file such that more than a single design file can import these declarations and thus always use the same declarations. The main advantage being that a change of any of these declarations has to be done only once and is applied immediately to all those design files which import these declarations.
The PXLab parser allows importing design nodes from other files. These files must contain a list of design node declarations. Not all nodes may be imported and there are some restrictions as to where importing statements are allowed. The general syntax of the Import statement is
Import( "directory-name", "file-name");
This imports the design node list from file file-name contained in directory directory-name. If the directory name is "." then the file to be imported must be in the same directory as the importing file. The Import statement is allowed
The following example shows several Import statements: The first one imports a Session display list declaration and the second one imports a display declarations.
Design File import.pxd
Experiment()
{
Context()
{
AssignmentGroup()
{
SubjectCode = "pxlab";
}
Import(".", "import_session.ipxd");
Block(Trial.Arrow.RightHeadLength, Trial.Arrow.ShaftWidth)
{
}
Trial( TrialCounter, Direction, Feedback.CorrectCode, Color, Feedback.Response, Arrow.ResponseTime)
{
ClearScreen:pause()
{
Timer = de.pxlab.pxl.TimerCodes.CLOCK_TIMER;
Duration = 1000;
}
FixationMark()
{
Timer = de.pxlab.pxl.TimerCodes.CLOCK_TIMER;
Duration = 500;
}
ClearScreen:wait()
{
Timer = de.pxlab.pxl.TimerCodes.CLOCK_TIMER;
Duration = 300;
}
Import(".", "import_arrow.ipxd");
Nothing()
{
Timer = de.pxlab.pxl.TimerCodes.CLOCK_TIMER;
Duration = 1000;
}
}
}
Factors()
{
IndependentFactor( Direction, Trial.Arrow.Orientation)
{
FactorLevel( "left", 180);
FactorLevel( "right", 0);
}
IndependentFactor( Color, Trial.Arrow.Color)
{
FactorLevel( "red", [21.26, 0.621, 0.34]);
FactorLevel( "green", [21.26, 0.3, 0.55]);
}
DependentFactor( Trial.Arrow.ResponseTime);
DependentFactor( Trial.Feedback.Response);
}
Procedure()
{
Session()
{
Block(180, 120)
{
Import(".", "import_trials.ipxd");
}
Block(270, 60)
{
Import(".", "import_trials.ipxd");
}
}
}
}
Here is the content of the file declaring the Session display list.
Design File import_session.ipxd
Session() {
Instruction() {
Text = ["Left / Right Response Time Measurement",
" ",
"Please press the left or right mouse button as fast as you can, depending on the direction of the arrow you will see.",
" ",
"Press any key now to start!"];
}
}
SessionEnd() {
Message() {
Text = ["E N D",
" ",
"Thanks for your participation."];
Timer = de.pxlab.pxl.TimerCodes.CLOCK_TIMER;
Duration = 2000;
}
}
And here is the content of the file defining the actual stimuli.
Design File import_arrow.ipxd
Arrow()
{
Timer = de.pxlab.pxl.TimerCodes.RESPONSE_TIMER;
ResponseSet = [de.pxlab.pxl.KeyCodes.LEFT_BUTTON, de.pxlab.pxl.KeyCodes.RIGHT_BUTTON];
RightHeadWidth = 180;
RightHeadLength = 180;
ShaftWidth = 120;
ShaftLength = 200;
}
ClearScreen:afterArrow()
{
Timer = de.pxlab.pxl.TimerCodes.NO_TIMER;
}
Feedback()
{
ResponseParameter = "Trial.Arrow.ResponseCode";
CorrectText = "%Trial.Arrow.ResponseTime@i% ms";
FalseText = "";
Evaluation = de.pxlab.pxl.EvaluationCodes.COMPARE_CODE;
Text = "%Trial.Arrow.ResponseTime% ms";
Timer = de.pxlab.pxl.TimerCodes.NO_TIMER;
}
Smiley()
{
Execute = Trial.Feedback.Response != de.pxlab.pxl.ResponseCodes.CORRECT;
Mood = Trial.Feedback.Response;
Overlay = de.pxlab.pxl.OverlayCodes.TRANSPARENT;
Timer = de.pxlab.pxl.TimerCodes.NO_TIMER;
}
The last file to be imported contains a list of trials.
Design File import_trials.ipxd
Trial( ?, "left", 0, <"red", "green">, ?, ?);
Trial( ?, "right", 1, <"red", "green">, ?, ?);
This shows that imported nodes have to follow the same syntax as if these same nodes were contained in the importing file. The imported text is simply inserted at the Import statement's position. Importing is done when the parser reads the main design file. If it sees an Import statement it will create a new parser for the file to be imported and this parser instance will parse the imported file and return the node list created. On return the main parser adds this list of design nodes to the node which is currently being created.
Imported nodes are always imported exactly as defined in the design file. In some cases it may be necesary to change the behavior of an imported node depending on the importing file. This can easily be done by using experimental parameters as is done in the above example. The imported display node Arrow uses two parameters named RightHeadLength and ShaftWidth. These are declared to be Block parameters. This makes it possible to use different parameter values for these two parameters for the two different imports in the two Blocks of the experiment's Session declaration.