Introduction
This documents helps you get started in
managing behavioral, psychological and physiological experiments in
BrainStim, before you start reading it's advised to first walk
trough the
Getting Started Guide of BrainStim if you not have done this.
Almost all discussed features of this guide are implemented in a
single plugin of BrainStim that is called the Experiment Manager
plugin which is always available from within BrainStim.
There are various ways of how you can create and run your
experiments, therefore depending on your needs you should choose the
implementation that suits your need best. Also the knowledge and
level of expertise of the user may limit to which extend the
experiment can be customized. An advanced users with a high level of
expertise and scripting experience can probably adapt the experiment
more to its needs than a user with novice knowledge and without any
scripting experience. This document tries to help you getting
started in creating and running your own experiments, starting from
the most basic level, and to help you make decisions regarding the
implementation for your experiment needs.
Implementation considerations
Depending on your experimental needs you can
choose between two implementations for your experiment. These are Retinotopic Mapping or
QML -based implementations.
Implementation |
Description |
Retinotopic Mapping
based |
Allows for an very fast
and accurate timed(frame-rate) visual
presentation
of various stimuli and is mainly used for creating
Retinotopic Mapping stimuli experiments,
read
this document for more information about this
topic. It therefore mainly
offers support for only visual media presentation
capabilities and customization can be achieved by
configuring the fixed set of available parameters.
For advanced users with enough scripting knowledge it can
also be used inside a script for an very fast and
accurate timed custom drawing of graphics to the
screen that do not always have to be part of a Retinotopic
Mapping experiment. |
QML based |
Allows for an fast presentation
of all kind of stimuli (like graphics, movies, audio,
3D content...) and can make use of almost any device
or feature available by default or through one of the
available plugin(s). QML-based experiments don't require
much scripting knowledge by default because almost
everything can be configured through the User Interface.
It can be highly flexible and customized for almost any
experimental need through scripting. |
Both implementations can make use of one or more
file(s) that can be of a different file type(extension). Some of
these file types can be used for both implementations, these are:
File-type (extension) |
Description |
Used for |
Experiment Structure
file(*.exml) |
a sort of XML file type that describes
mainly the structure of the
experiment. By configuring the
experiment structure you can divides the experiment in
smaller parts (like block, trials, triggers...) to fit
your timing needs. By doing this you can define when (and
how often) something should happen within one experimental
run. Editing of this type of file is made easy through the
UI support and capabilities, advanced users may also
choose to use BrainStim or another text editor for editing
this file. |
Retinotopic Mapping and QML-based
experiments |
QML file(*.qml) |
a User Interface
specification and programming language
that's developed and maintained within the
Qt
framework. It allows users
to create highly performing,
flexible and multi-capable stimuli presentation screens. Editing
of type of file can be done within BrainStim's code editing
capabilities. Less advanced users can also make use of
third party tool that allows you to create these stimuli
presentations with a easy WYSIWYG (what you see is what
you get) editor. |
QML-based experiments |
Script file(*.qs) |
is based on the ECMAScript scripting language
(Microsoft's JScript, and Netscape's JavaScript are
also based on the ECMAScript standard).
If you are not familiar with the ECMAScript
(or JavaScript) language, there are several
existing tutorials and books that cover this subject. |
Retinotopic Mapping and QML-based
experiments |
Depending on your scripting capabilities and
experiment requests you'll need to make use of one or more file(s)
for your experimental design. Each from the above files can be
executed directly in BrainStim to see the execution result, some
files may link to another file (or use a resource from another
file). The script file can be used to programmatically execute one
of the other files and bind everything together. There is also some
standard script code available that can be included inside your own
experiment script. This code makes some standard experiment features
available that can then be further easily configured from a custom
script. These features are then quickly available for users who
don't have much scripting experience or advanced users. Sometimes
the below examples might be a little difficult to follow because
they always start from scratch, normally if you create your own
experiments you would probably first start by copying some
experiment files and then only adapt them for your needs.
Examples
We'll now take a look at some basic examples
that show how different implementations as discussed above are used.
Retinotopic Mapping examples
Retinotopy is the mapping of visual input from the retina to neurons,
read this document for more information about this topic.
Different
types of stimuli presentations can be used for these types of
Retinotopic Mapping experiments. This example makes use of a
rotating wedge (Polar Angle).
Making use of the Experiment Structure file
For this example we'll be using only one file for the
whole experiment, namely the Experiment Structure file (*.exml). If
you don't succeed to create a valid Experiment Structure file by
following the below steps don't worry you can find the Experiment
Structure File solution from this example saved as PolarAngle.exml located in the
Main Program Directory under the subfolder \BrainStim\Retinotopy
Basics.
- Start BrainStim.
- Create a new Experiment Structure file using
the menu entry File > New > EXML
Document
- We can give our Experiment a name
by changing the Experiment name field the
Selection
tab of the Parameter List View which is
located by default at the right part of the screen. Change the
value to "Polar Angle Experiment" (without the quotes).
- Save the Experiment Structure file using the menu entry
File > Save to a new empty Experiment Structure
file.
Now we need to think about our experiment timing,
especially
when, how long and with
how many repetitions something needs to happen.
For this example we first want to present a fixation dot for 4 seconds. After this we want to show a rotating wedge that should rotate once in let's say 20 seconds and repeat this in total
5 times. After this we again want to present a fixation dot for 4 seconds
were after the experiment should finish.
- So let's define two blocks, one for the
fixation dot presentation and one for the polar angle animation.
Click somewhere in the Graphical Schematic "Blocks" View
with you right mouse button. A menu pops up and then we can
choose Blocks > Add New and do this
twice or alternatively choose Blocks > Add New(multiple) and enter value
2
in the Blocks to add: field.
Both should create 2 new blocks and we should now be able to see
them in the Graphical Schematic "Blocks" View
and also in the Parameters Table View.
- Let's configure the newly added blocks:
- Double click the Block: Name
field for Block Nr. 0 in the Parameters Table View and rename the Block name to
"Fixation" (without quotes) and select another field to
apply the changes, do the same for the second block, rename
it to "Polar Angle". Alternatively you can edit the same
fields by selecting the block you wish to change in the
Graphical Schematic "Blocks" View and then
change the corresponding value in the Selection
tab of the Parameter List View.
- Now we want to make sure that the
number of trials for each block is correct. The first
"Fixation" block only needs 1 trial (default value) so we
don't need to change this, but for the second "Polar Angle"
block we want 5 repetitions (repeats) of full Polar Angle
rotations so we need to change the Trials
field for the second "Polar Angle" block to 5,
do this now.
- One full Polar Angle rotation should
take 20 seconds.
Important:
In BrainStim we never specify the time it takes for one trial
to complete in
amount of seconds, but in
amount of triggers! This is
because BrainStim can then be triggered to go to
the next situation without knowing how log it actually took.
So it's acting independent
of its internal timing and instead waiting for trigger commands to do something. The
trigger can then be generated by an external device
connected to this computer and BrainStim is then
automatically synchronized
at each trigger point with that device
without any unwanted jittering to
build up.
Later on we'll make use of a (software) timer for this
example that sends out a trigger signal every second, but
this could be easily replaced by something else. Because 1
trigger takes 1 second in this example we can now set the
Internal
Triggers field for the "Fixation" block to 4 and for the "Polar Angle" block to
20
(because each trial should last for 20 seconds -->
20 triggers).
Important:
In BrainStim we specify the time it takes for one Block
Trial to complete in amount of Internal and
External triggers. Normally you only need to specify the
Internal triggers and leave the External triggers
setting to the default value of 1. This is because the
External triggers setting specifies how many
External triggers
it takes for the Internal trigger count to
increment (with 1 step). Therefore you set
the External triggers setting to 2
and the Internal triggers settings
to 3, then it takes in total 2 x 3 = 6
triggers for 1 BlockTrial
to complete. For our example we can now leave our External triggers
setting 1.
- Save the Experiment Structure file again using the
key combination Ctrl + s
In the above steps we defined using the Experiment Structure
file when,
how long and with
how many repetitions something needs to happen for our
experiment. These questions you always need to ask yourself when
defining a Experiment Structure file.
- Because for this example we only make use
of one file for the experiment we now also have to define
what objects we're going to use
and how to configure and connect
them. Because we're designing a Retinotopic Mapping Experiment
we're going to make use of a dedicated object that is designed
for presenting different Retinotopy stimuli. Let's do that first:
- Inside the Graphical Schematic View
you can make use of the dropdown combobox (View) to
switch the view to show Objects, after this
the Graphical Schematic "Objects" View is
empty because no objects have been defined yet. Right click
with your mouse and select the menu entry Objects >
Configure Object(s) , this opens a dialog where
you can add new objects. Our object that can present
different Retinotopy stimuli is from the Class
RetinotopyMapper, you should be able to
select this item from the Class drop down combobox.
We can now give this object a name using the Name
text entry like "RetinoMapper_Object" (without the quotes,
spaces are not allowed here!) and click the Add
button to add our object. We now see in the Declared
Objects list our newly added object, if we select it
here we can see even more configuration options. Close the
Configure experiment object(s) dialog using the
Close button. We can now see how our declared
"RetinoMapper_Object" object is visible inside the Graphical Schematic "Objects" View.
- Now we can configure our newly added
object, by setting its available parameters (for each
defined block).,
this document
describes the available parameters for the
RetinotopyMapper
Class. We know that
there are parameters that can be configured for this object
because of the presence of the parameters
icon inside the graphical representation of our newly
added object. Select the "RetinoMapper_Object" object inside
the Graphical Schematic "Objects" View and
right click and choose the menu entry Initialized
Parameters. Notice how the Selection
tab of the Parameter List View changes to a
hierarchical list view where we can configure all available
parameters from our "RetinoMapper_Object" object. It could
be handy to change the size of the Parameter List
View a little bit (make it wider). The available
parameters have a default value (which are used until you
change them) set and can be changed per defined block (we
defined two blocks in one of the above steps, a "Fixation"
and a "Polar Angle" block).
You may have noticed the name of
the just selected menu entry Initialized Parameters,
it's called like this because these parameters always refer
to the first block (the "Fixation" block in this example)!
In the Parameters Table view we can see all
the configured parameters, because none of them have changed
none of them are shown in this table. Lets change a
parameter for the first block in the Selection
tab of the Parameter List View; change the
Global > Pattern parameter (from the
default PolarAngle value) to
Fixation and notice how this value is now also
shown in the Parameters Table view because
we have changed it for the first "Fixation" block. Changes
here are colored in red text here and notice how the new value is
maintained (colored with a grey text) for the second "Polar
Angle" block! Change this pattern parameter value
for the "Polar Angle" block to PolarAngle
in the Parameters Table view by double
clicking the field you wish to change and then change it to
the PolarAngle value.
Important! Take a
look at the Selection tab of the Parameter List View
and try to discover that the set of editable parameters here
can change due to the current pattern parameter
value. This way the pattern parameter value filters
out only the parameters
that are relevant for the
current setting. This
automatic filtering of parameters is set internally.
- Save the Experiment Structure file again using the
key combination Ctrl + s
Now we still need to declare another object that is responsible
for creating the triggers at a specific timing interval (each
second for our example).
- Right click with your mouse in the
Graphical Schematic View and select the menu entry Objects >
Configure Object(s) . Add another object from the
Class TriggerTimer and name it
"TriggerTimer_Object" (without the quotes). Click the
Close button to close the
Configure experiment object(s) dialog again. Notice
how the newly added object is also graphically shown in the Graphical Schematic "Objects" View.
We now do not see a parameters
icon inside the graphical representation of our newly added
object. This means that there are no specific parameters are
available to set for this object for each defined block. Let's
open the
Configure experiment object(s) dialog again and see
what we can configure there. In this dialog we can select the
newly added "TriggerTimer_Object" object in the Declared
Objects listview. We then see a tabular area with three
tabs were we can further configure our selected object. In the
Declaration tab we can rename our selected
object. In the Initialization and
Finalization tab we can define some function(s) (also
called slot(s)) that are either automatically called at the
beginning (Initialization) or the end (Finalization)
of our experiment, thus in this example before the first
"Fixation" block or after the second "Polar Angle" block. These
defined slot(s) can then configure our object the way we want it
to be, the available slot(s) are shown in the Available
dropdown list and are always documented in the Help (in this
case you would search in the Help for "TriggerTimer" (without
quotes) keyword). This should then take you to
this page
that describes the whole TriggerTimer class,
search for the "startTimer" (without quotes) slot and see what
it does, it should mention something like:
Starts the Trigger Timer. This function starts the Trigger
Timer and then automatically emits a TriggerTimer::timeout() signal
when triggered.
- Parameters:
-
dMSec |
the period trigger time in
milliseconds. |
Open the Initialization tab and select the
startTimer(double dMSec) slot, click the
Add button to add this slot call to the
initialization of out experiment. Now the newly defined slot
appears in the Defined list, select it here and
click the Configure Argument(s) button. This
opens another dialog where you can configure the argument(s) for
our newly defined slot. There's only one argument available for
this slot (named dMSec and of the
double type), this argument sets the period trigger
time
in milliseconds. For this experiment we would like to make the
trigger time 1 second so we'll set the dMSec value to
1000 and click the Update and
Close button afterwards.
We also need to stop the automatic triggering from the declared
"TriggerTimer_Object" object whenever the experiment is
finalized (or aborts/stops). This can be done inside the
Finalizations tab of our "TriggerTimer_Object" object.
Add a slot named stopTimer(), you
don't need to specify argument(s) for this slot because there
are none, see documentation.
Close the Configure Experiment object(s) dialog and save your
changes using the key combination Ctrl + s
Now the "TriggerTimer_Object" object still needs to tell our
"RetinoMapper_Object" object whenever it's triggered and the
"RetinoMapper_Object" this is done through a signal/slot
connection. We'll use the timeOut() signal
which is automatically emitted by the "TriggerTimer_Object"
object when triggered, see (above) documentation. And we'll
connect it to the incrementExternalTrigger() slot of the
"RetinoMapper_Object" object, see this
documentation page.
- Right click with your mouse in the
Graphical Schematic View and select the menu entry Connections >
Configure Connection(s) , this open up a new
dialog that allow you to make connection(s) between declared
object(s). Using signal/slot connections inside a script
is documented in
this document,
for the user that doesn't have much scripting experience using
the following method is probably easier.
Select our created "TriggerTimer_Object" object using the
From object combobox selection list, after this
you can select the timeout() signal from the
corresponding Method combobox selection list.
Now select our created "RetinoMapper_Object" object using the
other To object combobox selection list, after
this select the incrementExternalTrigger()
slot from the other corresponding Method
combobox selection list and click the Add
button. Now the connection is made and we can close the
Configured Object Connections dialog. In the Graphical Schematic "Objects" View
we can now see the above defined "TriggerTimer_Object"
Initializations/Finalizations and the connection to the
"RetinoMapper_Object" object.
- Save the Experiment Structure file again using the
key combination Ctrl + s
Now it's time to execute our document and see what it does.
Before we execute the document we need to know about the special
key combination that allows us to abort/stop the experiment at
all times when it's running, this is the Ctrl +
a
(a=abort) key combination. If you execute the document then it
will first be in a locked state and with the Alt
key you can then unlock it. When it's unlocked it the waits for
the first trigger and then at that point the experiment is
started, try this by pressing the green
Execute
button and watch what happens, remember to press Ctrl +
a at any time to abort.
- Now we're going to change some parameters
for the stimuli presentation. Switch to the Objects
view for the
Graphical Schematic View select the
"RetinoMapper_object", right click it and select the menu entry
Initialized Parameters . Now we can make some changes
to the parameters inside the Parameters List View,
change the following parameters:
Global > Size > Width: change this setting to
your the height of your own screen resolution, we fill in the
height instead of the width here because we want of perfect
square stimuli area.
Global > Size > Height: change this setting to
the same value as the Height.
Now we need to make a change to one of the "RetinoMapper_object"
second "Polar Angle" block parameter. We can do this by
selecting inside the Parameters Table View a
parameter from the "RetinoMapper_object" of the second "Polar
Angle" block. If we do this then the Selection
tab of the Parameter List View shows us all the
parameters from the "RetinoMapper_object" of the second "Polar
Angle" block and now we should be able to make the following
change:
PolarAngle specific > Number of Checkers: change this
setting to 6.
PolarAngle specific >
Direction: change this setting to
CounterClockwise.
PolarAngle specific > Trigger
Duration: change this setting to
1000. Important! When
you executed the document you probably noticed that the rotation
of the wedge was in the beginning smooth and then suddenly
stated to jump. This was because the Trigger Duration
parameter was still set to the default value of
2000 milliseconds. This value specifies how
fast the wedge should turn smoothly between two triggers and
because the rotation of the wedge is synchronized at each
trigger it then starts to jump with a false value, therefore we
need to configure it so it matches the trigger signal of our
"TriggerTimer_Object" object which is 1000
milliseconds.
Now it's again time to execute our document and see what our
above changes lead to. Press the green
Execute
button and watch what happens, remember to press Ctrl +
a
at any time to abort. The wedge should now rotate smoothly all
the time and the stimulation area should now nicely fit your
screen. There should also be more checker (rows) visible and the
wedge should turn counter clockwise.
You probably also noticed in the top of the screen some
additional information about the current state of the internal
experiment structure at each trigger this is automatically
updated to the current situation which can be very useful for
debugging. Another useful key combination you can use for
testing purpose after the experiment has been unlocked is Ctrl +
t (t=trigger), this key combination automatically
invokes the incrementExternalTrigger()
slot each time this combination is used (you can also hold down
this key combination for multiple invocations), allowing you to
quickly jump to the next Experiment Structure state manually.
At some point we want to execute our experiment with
a real subject and do not want this information to appear at the
top of our screen, we can simply disable this by changing the
Debug mode
for our experiment by selecting the
Experiment tab in the
Parameter List
View
and then disabling this setting.
Important! Remember to save
your changes before executing the document again.
Aside Blocks, Trials and
(internal/external) Triggers we can also make
use of Block Loops in our Experiment Structure
file. These Loops allow use to jump to another Block when the
current Block is finished instead of going to the next Block
(determined by BlockNumber), furthermore we can specify how
often this should happen, let's try this:
- Switch to the Blocks
view for the
Graphical Schematic View, right click it and select the menu entry
Loops >
Configure Loop(s) , this opens a dialog where we
can manage our defined Loops. Let's create a
loop that is executed at the end of the first block and then
jumps back at the beginning of the first block and does this
twice:
- In the Create Loop area make sure to select the
first block "Fixation" for the From but
also To dropdown selection box and the
click the Add button
- Change the Name property to
"FixationLoop" (without the quotes)
- Leave the Repetitions
property set to 1
- Press the Close button to close the
dialog again
- We can now see our newly added Block Loop, the order of
execution of our blocks has now been changed due to this new
Loop definition. If we execute the experiment we'll see first
that Block 0 gets executed, after that the loop is executed and
again Block 0 gets executed (including all of its defined
Trials/Triggers) and hereafter finally Block 1 gets executed
because the Loop is only repeated once (see
Repetitions property!).
Important!
Although we set the Loop Repetitions property to
1 Block 0 got executed twice! This is because our Loop gets
executed AFTER Block 0. Please
bear in mind that due to this if you want to repeat one or more
Block(s) using a Loop you should always have to set the Loop
Repetitions property to the amount of needed
block repetitions MINUS 1!
- Let's execute our document and see what our
above changes lead to. Press the green
Execute
button
- Open the Configure Block Loop(s) dialog again using the menu
entry Loops >
Configure Loop(s) and remove our newly created
Block Loop again by selecting it from the Configured Loops
listview and pressing the Remove button.
- Save the Experiment Structure file again using the
key combination Ctrl + s
The above showed us how to create/execute
and change a basic
Retinotopic experiment using only one Experiment Structure file.
It took many steps to accomplish this and normally you don't
need to follow all of these steps but would start with copying
a Experiment Structure file and then simply adapt it to your
own needs or perhaps better make use of a script include as
we'll see next.
Important! You
can find the Experiment Structure File solution from this
example saved as PolarAngle.exml located in the
Main Program Directory
under the subfolder \Examples\BrainStim\Retinotopy
Basics.
Combining the Experiment Structure file with
a script include
You'll get the most advantages if you combine the Experiment
Structure file with a custom script, this is also the advised
strategy to use. For the next example we'll be making a similar
Retinotopy experiment as above by creating again a Experiment
Structure file (but now without the
TriggerTimer Objects, (F)(In)-initializations and
Connections) and add a custom script file (*.qs) that
includes a template script file to
accomplish the same as above (plus more...).
This include template script ships with BrainStim, you only need to
refer to it and suit it to your needs in your own custom script (you
should never change this template script file itself). The template
script It is fully tested and optimized to support Retinotopy
experiments. By including this template script file you get more
overview, flexibility and control over your experiment.
Let's first again create a Experiment
Structure file as above but now without declaring the
TriggerTimer Object and without configuring
(F)(In)-initializations and Connections (because this is already
implemented for us in the template script file so we don't need to
worry about this). The first steps of this example are almost
similar as the steps from the previous example but for clarity
reasons I'll summarize them again. The resulting Experiment
Structure file is saved to the file PolarAngle_ScriptRef.exml that is located in the
Main Program Directory under the subfolder \Examples\BrainStim\Retinotopy
Basics\.
Also the custom script file is saved there under the name
PolarAngle_ScriptRef.qs. Let's first create the Experiment Structure
file:
- Start BrainStim.
- Create a new Experiment Structure file using
the menu entry File > New > EXML
Document
- Give the Experiment a name
by changing the Experiment name field the
Selection
tab of the Parameter List View, change it to "Polar Angle Experiment" (without the quotes).
- Click somewhere in the Graphical Schematic "Blocks" View
with you right mouse button and choose the menu entry Blocks > Add New(multiple)
to add
2
Blocks.
- Let's configure the newly added blocks:
- Double click the Block: Name
field for Block Nr. 0 in the Parameters Table View and rename the Block name to
"Fixation" (without quotes). Do the same for the second
block, rename it to "Polar Angle".
- Change the Trials
field for the second "Polar Angle" block to 5.
- Change the
Internal
Triggers field for the "Fixation" block to 4 and for the "Polar Angle" block to
20.
Now we should again declare a Object from the
RetinotopyMapper class so we can the set its
parameters. Our template script, that we'll include later
on in our custom script, searches automatically for an declared
object from the RetinotopyMapper class and use it.
- Switch the view of the Graphical Schematic View to show
Objects. Right click with your mouse in the
Graphical Schematic "Objects" View and select
the menu entry Objects >
Configure Object(s) . Add a new object from the
Class RetinotopyMapper and name it "RetinoMapper_Object" (without the quotes). Close the
Configure experiment object(s) dialog using the Close button.
Now we can again configure our newly added
object, by setting its available parameters (for each
defined block). Important! This time we'll
not set the parameters to a fixed value but link it to a certain
variable inside our custom script, making it easy to change
this parameter through our own custom script.
- Select the "RetinoMapper_Object" object inside
the Graphical Schematic "Objects" View and
right click and choose the menu entry Initialized
Parameters. Select the Global > Pattern
parameter in the Selection
tab of the Parameter List View. When
selected you may notice the lock
icon on the right side of the editable area. This
locked lock means that that
parameter value is fixed to the value you entered here (or the
previous block and otherwise default value). You can unlock
it by clicking it, do this now. The editable area now contains a
text input box where you can add a script reference.
This script reference is text that contains some script
syntax which is
executed and should resolve the value for the parameter each time it is
fetched (for each block trial). Enter the text
"RetinoMapper_Global_Pattern" (without quotes) here and press
the Enter key. You may have noticed that the entered text now is
surrounded curly brackets, like
{RetinoMapper_Global_Pattern}, these curly brackets let
you know that this parameter value is linked to some script code
that is inside the curly brackets. We now only have to make sure
to add a script variable with the same name
RetinoMapper_Global_Pattern so we can use it to change the
parameter value. Important! bear in mind that
parameter values are fetched at each new Block Trial, so
changing a parameter value such way as described above here only
has use if you do it just before the new value is fetched.
!Important:
Notice that the automatic filtering for the pattern
parameter (as seen in the previous example) is omitted when we
changed the setting of the pattern parameter to a
script reference value.
-
Let's unlock some more parameters and refer
them to a script reference value like:
Global > Size > Width: change this
setting to {RetinoMapper_Global_Width}
Global > Size > Height: change this
setting to {RetinoMapper_Global_Height}
Global > Fixation Point > Color: change this
setting to {RetinoMapper_Global_FixColor}
PolarAngle specific > Number of Checkers: change this
setting to {RetinoMapper_Polar_NrOfCheckers}
PolarAngle
specific > Direction: change this
setting to {RetinoMapper_Polar_Direction}
PolarAngle
specific > Trigger
Duration: change this
setting to {RetinoMapper_Polar_TriggerDuration}
-
Save the Experiment Structure file again using the
key combination Ctrl + s to a file named
"PolarAngle_ScriptRef.exml" (without the quotes).
Remember that
this document describes
the RetinotopyMapper Class and it's available
parameters. We've now configured all script references that we want
to be able to change in our custom script. By adding script
references to the Experiment Structure file it is not possible
anymore to run the Experiment Structure file directly from the
BrainStim User Interface (by pressing the green
Execute
button) because there's no script that can resolve our
script reference(s). For this to work we need to execute it
from a script, let's do that next:
-
Create a new script file using
the menu entry File > New >
QtScript
Document
-
Save the Script file to the same directory as
were the Experiment Structure file is saved to, using the
key combination Ctrl + s , to a file named
"PolarAngle_ScriptRef.qs" (without the quotes).
For running a Retinotopy experiment from a script file it's
advised to include a template script that implements a lot of
experimental features for us that we need, allowing us in the
beginning to focus only on fine tuning the experiment. But if
you want then you can override everything from the template
script file inside your own custom script or completely write
your own custom script for running a Retinotopy experiment
without making use of the template script. Template script files
are part of the BrainStim Include files, read
this document for more information about Includes and how to
use them. Important: you should never
delete/move or change these Include files! For this example
we'll include a script file named
"BasicRetinotopyExperiment.qs", that internally again includes
another file named "BasicExperiment.qs". Let's write our first
line of code were we'll include the template script:
Include("QtScript/BasicRetinotopyExperiment.qs");
|
- Now that we Included the "BasicRetinotopyExperiment.qs"
template file we have a new script object available which has a
lot of properties that we can use to configure our experiment.
This script object is named BasicExperiment and
is actually first created inside the "BasicExperiment.qs"
template file that is again included by the
"BasicRetinotopyExperiment.qs" template file. The
"BasicRetinotopyExperiment.qs" template file then takes the
BasicExperiment object and extends(or
overrides) it with some additional (Retinotopy) features which
is now again available for us in our custom script, let's
configure two properties for this script object:
BasicExperiment.sExmlFilePath = BrainStim.getActiveDocumentFileLocation() + "/" + "PolarAngle_ScriptRef.exml";
BasicExperiment.nTestModeTriggerDuration = 1000;
|
First we set the file-path for our Experiment structure file
that we want to make use of. This file is saved in the same
directory of our custom script file and so we can make use of
the function
BrainStim.getActiveDocumentFileLocation() for
retrieving the directory path, see
this document.
After that we'll set the trigger time for a trigger simulation
timer. This is a feature is implemented by our template script,
it implements a TriggerTimer object that automatically
triggers our experiment by invoking incrementExternalTrigger()
slot like we saw in the previous example for us. With the
BasicExperiment.nTestModeTriggerDuration we can
set the trigger time that should be used for our experiment for
a test mode(we'll see later what this test mode exactly does).
We have unlocked some parameters and set them to refer to a
script variable, and now we have to make sure that these exist
in the script, lets declare and give them a initial value like:
var RetinoMapper_Global_Pattern = "Fixation";
var RetinoMapper_Global_Width = 800.0;
var RetinoMapper_Global_Height = RetinoMapper_Global_Width;
var RetinoMapper_Global_FixColor = "#00FF00"; //Green color
var RetinoMapper_Polar_NrOfCheckers = 6;
var RetinoMapper_Polar_Direction = -1 //CounterClockWise
var RetinoMapper_Polar_TriggerDuration = 1000.0;
|
Now we have entered enough script code for running the
experiment, which leaves us at the last pieces of code that runs
the experiment:
BasicExperiment.RunExperiment();
|
- Save the Script file again using the
key combination Ctrl + s
- Now we can execute our custom script document. You'll be
first asked with a dialog how you want to run the experiment
(the Experiment Mode), you can choose between a Test
or Optimized mode (the included script template
implements this automatically for us). The difference between
both modes is that
Test mode generates a lot of additional
debugging information for you to use if you need to test things.
It also creates and automatically starts a object constructed
from the TriggerTimer class like we
saw in the previous example that automatically triggers the
experiment for you (testing purpose). It uses the
BasicExperiment.nTestModeTriggerDuration
setting for setting the trigger interval time in milliseconds.
Optimized mode should be used for the final
critical experiment when no more testing is needed and therefore
additional test code should not be executed. Optimized
mode doesn't use by default a defined object for the triggering,
so this is still something we'll need to configure, for now you
can make use of the Ctrl +
t (t=trigger) key combination to manually invoke the incrementExternalTrigger()
slot. After you choose the Experiment mode the experiment
will again first enter a locked state and with the Alt
key you can then unlock it. When it's unlocked it the waits for
the first trigger and at that point the experiment is started.
Also remember that we can still use the special key combination
that allows us to abort/stop the experiment at all times when
it's running, the Ctrl + a (a=abort) key
combination.
Execute the script file by pressing the green
Execute
button and see what happens.
- Although we declared and initialize the script references
(variables) they didn't automatically change/adapt to the
current experiment structure while the experiment running. This
is something we still need to do, the
RetinoMapper_Global_Pattern script variable for
example needs to change from "Fixation" to "PolarAngle" for the
second block (BlockNumber 1). There's a common solution that
let's you write your own custom script code inside a function
that is automatically called just before the current experiment
structure changes to a new Block or Trial. This feature is again
implemented by our included script template, we can then
override/define it with our own custom script code by adding the
following piece of code to our custom script that overwrites the
BasicExperiment.PrepareNewInitBlockTrial
function:
BasicExperiment.__proto__.PrepareNewInitBlockTrial = function()
{
}
|
Now we have an entry point that is automatically executed each
time just before the experiment structure enters a new Block or
Trial. Remember that parameters and therefore
their referenced script variables are only fetched at the
beginning of a new Block or trial. Now we need to know inside
our overridden function which Block or Trial is soon to be
entered so we can for example change our script referenced
variables accordingly to the new values for that Block-trial.
Therefore we almost always add the following code inside
(between the curly brackets {}) our overridden function:
BasicExperiment.__proto__.PrepareNewInitBlockTrial = function()
{
var _currentBlockID = BasicExperiment.nCurrentExperimentStructureState.CurrentBlock_BlockID;
var _currentBlockNumber = BasicExperiment.cExperimentStructure_Object.getBlockPointerByID(_currentBlockID).getBlockNumber();
var _currentTrialNumber = BasicExperiment.nCurrentExperimentStructureState.CurrentBlock_TrialNumber;
}
|
Those three lines retrieve the Block and Trial number.
Important! The BlockID is only
internally used and should never be used directly (because it
can change), always make use of the BlockNumber that you can
retrieve by passing the BlockID to a function
getBlockPointerByID() like the above code shows. Now we
have both the current Block and Trial number we can make use of
them and for example change our script referenced variables
accordingly like:
BasicExperiment.__proto__.PrepareNewInitBlockTrial = function()
{
var _currentBlockID = BasicExperiment.nCurrentExperimentStructureState.CurrentBlock_BlockID;
var _currentBlockNumber = BasicExperiment.cExperimentStructure_Object.getBlockPointerByID(_currentBlockID).getBlockNumber();
var _currentTrialNumber = BasicExperiment.nCurrentExperimentStructureState.CurrentBlock_TrialNumber;
BrainStim.write2OutputWindow("*** Going to prepare a initialization of a new Block-trial: BlockNr: " + _currentBlockNumber + ", TrialNr: " + _currentTrialNumber);
if(_currentBlockNumber==1)
{
RetinoMapper_Global_Pattern = "PolarAngle";
RetinoMapper_Global_FixColor = "#FF0000"; //Red color
}
}
|
- Save the Script file again using the
key combination Ctrl + s
- Execute the script file by pressing the green
Execute
button and see what happens.
- There's another convenient function implemented by the included script
template "BasicExperiment.qs" that is also often overridden in a
custom script. This function (ExperimentStateChanged)
is automatically called whenever the experiment state changes.
This experiment state is something different then the experiment
structure, the current experiment state specifies for example
when the experiment was started, initialized or stopped/aborted.
Furthermore it also passes a DateTime stamp of the state change
in a string format which you can make use of. You can again make use of this by overriding that function and
adding your own custom script code to it, let's try this:
BasicExperiment.__proto__.ExperimentStateChanged = function(currentState, sDateTimeStamp)
{
//This Function is called automatically at each time the Experiment State changes
//See Help documentation (search for "ExperimentManager::ExperimentState") for a description of the different states
if(currentState == ExperimentManager.ExperimentState.ExperimentManager_Initialized)
{
BrainStim.write2OutputWindow("*** ExperimentStateChanged to: Initialized at " + sDateTimeStamp);
}
else if(currentState == ExperimentManager.ExperimentState.ExperimentManager_Started)
{
BrainStim.write2OutputWindow("*** ExperimentStateChanged to: Started at " + sDateTimeStamp);
}
else if(currentState == ExperimentManager.ExperimentState.ExperimentManager_Stopped)
{
BrainStim.write2OutputWindow("*** ExperimentStateChanged to: Stopped at " + sDateTimeStamp);
}
}
|
- Save the Script file again using the
key combination Ctrl + s
- Execute the script file by pressing the green
Execute
button and see what happens (look at the "Default" Output Log
window).
QML Based examples
With the use of QML files we can create highly performing,
flexible and multi-capable stimuli presentation screens. Combined
with an Experiment Structure file and optionally a custom script
file we can create the experiment that suits our needs. The below
examples expect you to have gained some knowledge from the above
two tutorials because QML Based experiments are not much different
from Retinotopic Mapping based experiments and common parts are not
explained in detail as much as done in the previous Retinotopic
Mapping examples.
The QML file
The QML file is a User Interface specification and programming
language that allows us to create highly performing,
flexible and multi-capable stimuli presentation screens. Editing
of type of file can be done within BrainStim's code editing
UI capabilities or by making use of
another third party tool that allow you to create these stimuli
presentation screens with a easy WYSIWYG (what you see is what
you get) editor. The QML file type is developed and
maintained by Qt, read
this document from Qt for a description of the file. There are
also some nice QML tutorials there from Qt, they are located
here. But maybe the most detailed and best way to start learning
QML is to read the
QMLBook, a
great open source gitHub project with great examples and step by
step tutorials. The following examples don't require much knowledge
of the QML syntax but also don't explain it, so if you have
questions about this you should use one of the above links or use a
search engine providing the string "qml" followed by the topic of
your question.
The ExperimentManager plugin of BrainStim has an internal QML engine
that can execute QML
- directly (QML
document (*.qml)) from the BrainStim UI by pressing the green
Execute
button
- from a Experiment Structure file (*.exml) with the use
of an declared object from the
QML2Viewer
class
- from a script by creating a object of the
QML2Viewer class
The next examples will cover the first two usage options and
afterwards also a implementation where a include of a script
template file (called "BasicQMLExperiment.qs") is used, like we did
for the last Retinotopy experiment example.
Creating and executing the QML file
Let's start by creating a valid QML file and see what it's basic
elements are. The resulting QML file from this example is saved to
the file ImagesAndText.qml that is located in the
Main Program Directory under the subfolder \Examples\BrainStim\QML
Basics\.
- Start BrainStim.
- Create a new QML file using
the menu entry File > New > QML
Document
- Save the Script file again using the
key combination Ctrl + s give it a
filename like "ImagesAndText.qml"
- Add the following code inside the new document:
import QtQuick 2.0
Rectangle //Root element
{
width: 360
height: 360
color: "#dddddd"//RGB value ("#RRGGBB") Grey background color
Text
{
id: centerText
anchors.centerIn: parent
text: "Hello World"
color: "blue"
}
}
|
What we see on the first line is a import statement, this
imports a module in a specific version.
Important! In general you
always want to import QtQuick 2.0 as your initial set of
elements. Next we see an element of the type Rectangle
that contains another element of the Text type,
both of them have some property settings.
Some basic QML syntax rules to follow are:
- Comments can be made
using
//
for
single line comments or /* */
for
multi-line comments. Just like in C/C++ and JavaScript
- Every QML file needs to
have exactly one root element, like HTML
- An element is declared
by its type followed by
{ }
- Elements can have
properties, they are in the form
name : value
- Arbitrary elements
inside a QML document can be accessed by using their
id
(an
unquoted identifier)
- Elements can be nested,
means a parent element can have child elements. The parent
element can be accessed using the
parent
keyword
- Save the Script file again using the
key combination Ctrl + s
- Now we can Execute the script file by pressing the green
Execute
button. The previous save step could even be skipped because
only the text inside the document is used for execution and not
the saved text.
Important after your execute
the document it is presented by default in full-screen mode,
there's no close button for that window and way to abort
execution and close the window is to make use of the Ctrl + a
(a=abort) key combination. Now Execute the script file by
pressing the green
Execute
button and see what happens.
- We just saw a full-screen presentation of the QML file with
a grey background and
some blue text in the center of the screen, just as we defined.
The only 2 properties which did not work were the width
and height setting for our first (root)
element. The reason for this is because the properties are
automatically removed by the QML engine when the document is
presented using the default full-screen mode, so there's not use
for doing this. If you would like to show a smaller rectangle
displayed on the screen you should make another smaller
rectangle and make this a child element of the root rectangle
element.
- Let's add that child Rectangle element that
should then contain the
centerText Text element plus an
additional Image element:
import QtQuick 2.0
Rectangle //Root element
{
id: rootRectangle //a custom ID
color: "#555555"//RGB value ("#RRGGBB") DarkGrey background color
Rectangle //Child of Root
{
id: childRectangle
width: 400
height: 400
color: "#dddddd"//RGB value ("#RRGGBB") Grey background color
anchors.centerIn: parent //Center in its parent(rootRectangle)
Text
{
id: centerText
anchors.bottom: childRectangle.bottom//Make the bottom equal to that of childRectangle
anchors.horizontalCenter: parent.horizontalCenter//Center it horizontally in its parent
text: "Hello World"
font.family: "Helvetica"
font.pointSize: 20
color: rootRectangle.color//Set it to the same color of rootRectangle
}
Image
{
id: centerImage
anchors.centerIn: parent
source: "images/BrainStim.png"
}
}
}
|
Execute the script file by pressing the green
Execute
button and validate what you see is what you would expect from
the QML code.
- The QML engine of BrainStim also allows to create more media
like Movies, Audio and 3D content. Furthermore it can create a
dynamic scene that changes and allows for user interaction
(mouse, keyboard). Lets try that by adding some keyboard and
mouse interaction between the user and the QML file and use that
to change the centerText.text property. Add the following
code right after the closing curly bracket "}" of the childRectangle
element:
MouseArea //A Mouse Area item, that let's us capture mouse events and make it interactive
{
anchors.fill: parent //Here we make sure that the Area covers the whole rootRectangle
onClicked: //this function is automatically called when this area is clicked with the left mouse button
{
console.log("Mouse Clicked!") //A way of sending text to the BrainStim Output Log Pane (Default), used for debugging and testing purpose
if(centerText.text == "Hello World")
{
centerText.text = "Hello BrainStim";
}
else
{
centerText.text = "Hello World";
}
}
}
Item //For creating keyboard interaction we make use of a base type (Item) because it supports key handling
{
id: keyboardItem
anchors.fill: parent //Here we make sure that the Area covers the whole rootRectangle again
focus: true //Change the keyboard focus to this element
Keys.onPressed: //this function is automatically called when a key is pressed while this area has focus
{
if (event.key == Qt.Key_Escape) //Check if the escape key was pressed
{
Qt.quit(); //Call the 'Qt.quit()' function to automatically exit, same as CTRL + 'a' (abort)
}
}
}
|
Execute the script file by pressing the green
Execute
button and validate what you see is what you would expect from
the QML code. There are many more features that allow you to
make your QML scene dynamic, like by making use of
Animations,
PropertyBindings or
ShaderEffects but this is far beyond the scope of this
example.
- You can also define custom functions inside a QML
document, and this function can the be called by other objects
from your document. But most
importantly these function can the also be called from a
custom Script file, which gives us an sort of interface between
our custom Script file and QML file. Let's see how we can define
a function and use that from inside QML. Let's add two
functions, one that changes again the centerText.text
property and another function that can change the
centerImage.source property. Add the following code
after the closing curly bracket "}" of the keyboardItem
element:
Item
{
id: functionsItem
objectName: "functions"
function setTextFunction(sText)
{
console.log("setTextFunction() called with parameter:", sText)
centerText.text = sText;
return true;
}
function setImageFunction(sPath)
{
console.log("setTextFunction() called with parameter:", sPath)
centerImage.source = sPath;
return true;
}
}
|
We added the 2 functions together to a Item element type and
aside its Id property we also configured the
Item elements objectName property. We need
this unique objectName property later on for
accessing the functions from a custom script call. Now we can
already test these function by calling them from our already
defined Keys.onPressed function
handler, we only need to add some additional code to it so
afterwards it looks like:
Keys.onPressed: //this function is automatically called when a key is pressed while this area has focus
{
if (event.key == Qt.Key_Escape) //Check if the escape key was pressed
{
Qt.quit(); //Call the 'Qt.quit()' function to automatically exit, same as CTRL + 'a' (abort)
}
else if (event.key == Qt.Key_1) //Check if the 1 key was pressed
{
functionsItem.setImageFunction("images/BrainStim.png");
functionsItem.setTextFunction("Hello BrainStim");
}
else if (event.key == Qt.Key_2) //Check if the 1 key was pressed
{
functionsItem.setImageFunction("images/World.png");
functionsItem.setTextFunction("Hello World");
}
}
|
Try to understand the code and execute the script file by pressing the green
Execute
button
- Save the Script file again using the
key combination Ctrl + s
Making use of the Experiment Structure file
The steps followed in this example are almost the same described
in the same chapter for the Retinotopy example. The only difference
is that instead of declaring a object that can present
different Retinotopy stimuli (from the Class
RetinotopyMapper),
we'll be declaring a object that can present a QML file from the
Class QML2Viewer.
If the following steps are not clear enough for you to understand
please take a look at RetinotopyMapper example first. The result of
this example is saved to the file ImagesAndText.exml that is located in the
Main Program Directory under the subfolder \Examples\BrainStim\QML
Basics\.
Because this example is so similar we'll adapt the result of the
Retinotopy example and save it to another filename.
- Open the file PolarAngle.exml that is located in the
Main Program Directory under the subfolder
\Examples\BrainStim\Retinotopy Basics\ and save it under another name by using
the menu entry
File > Save As...
- Change the Experiment name field the
Selection
tab of the Parameter List View "Images and
Text Experiment" (without the quotes).
- Switch to the Graphical Schematic "Objects" View
and select the right click menu the entry Objects >
Configure Object(s)
- Select the RetinoMapper_Object from the
Declared Object(s) list and click the Remove
button.
- Add a new object from the QML2Viewer
class and rename it to "QML2Viewer_Object" (without
the quotes) and close the dialog.
- Now the "TriggerTimer_Object" object still needs to tell our
"QML2Viewer_Object" object whenever it's triggered and this is
again done through a signal/slot
connection. We'll use again the timeOut() signal
and
connect it to the incrementExternalTrigger() slot of the
"QML2Viewer_Object" object, see this
documentation page.
Right click with your mouse in the Graphical Schematic View
and select the menu entry Connections >
Configure Connection(s) and make the connection
from our "TriggerTimer_Object" using the timeout()
signal to the "QML2Viewer_Object" objects incrementExternalTrigger()
slot. Close the dialog.
- Change the experiment structure Block settings in the
Parameters Table View to:
Block: Name |
Block: Trials |
Block: Internal Triggers |
Block: External Triggers |
First Block |
1 |
2 |
1 |
Second Block |
2 |
4 |
1 |
- We again know that there are parameters that can be
configured for the "QML2Viewer_Object" object because of the
presence of the parameters
icon inside the graphical representation of this object. Right
click somewhere in the Parameters Table View and select the menu
entry Parameters >
Configure Parameter(s) . Select the
QML2Viewer_Object from the Object
selection list in the Parameter Selection. The
parameters now become visible in the Parameter
selection list, make sure the selection is set to Global / QML File
and enter a string in the Value textbox of the
Parameter Configuration section that holds the
relative path to the QML File from the previous example
"./ImagesAndText.qml" (without the quotes). Press the
Set button to set the new parameter value. Close the dialog and Save the Experiment Structure file again using the
key combination Ctrl + s
- Now it's again time to execute our document and see what our
above changes lead to. Press the green
Execute
button and watch what happens, remember to press Alt
to unlock and the Ctrl +
a at any time to abort. Notice the result of our
previous example was shown and that we were still able to
interact with the QML scene using our mouse or keyboard. If we
waited 2 + 4 = 6 seconds we probably also noticed that the QML
scene was automatically stopped when the experiment structure
ended. Now we want to make changes to the QML scene using the
two defined QML functions depending of the current state of our
experiment structure, the way to do this is to make use of a
custom script file that again included a script template file
like we saw in the Retinotopy example.
Combining the Experiment Structure file with
a script include
Again you'll get the most advantages if you combine the QML file with a custom script, this is also the advised
strategy to use. For the next example we proceed with the last
Experiment Structure file and add a custom script file (*.qs) that
includes a template script file to
again get more
overview, flexibility and control over your experiment.
Let's first again create a Experiment
Structure file as above but now without declaring the
TriggerTimer Object and without configuring
(F)(In)-initializations and Connections (because this is already
implemented for us in the template script file so we don't need to
worry about this). The first steps of this example are almost
similar as the steps from the previous example but for clarity
reasons I'll summarize them again. The resulting Experiment
Structure file is saved to the file PolarAngle_ScriptRef.exml that is located in the
Main Program Directory under the subfolder \Examples\BrainStim\Retinotopy
Basics\.
Also the custom script file is saved there under the name
PolarAngle_ScriptRef.qs. Let's first create the Experiment Structure
file:
- Open the file ImagesAndText.exml file from the
previous example, there's also a solution saved in the
Main Program Directory
under the subfolder
\Examples\BrainStim\QML Basics and save it under the name
"ImagesAndText_Scripted.exml" (without the quotes) by using
the menu entry File > Save As...
Again we don't need the "TriggerTimer_Object" Object because out
template file already implements that functionality.
- Switch to the Graphical Schematic "Objects" View
and select the right click menu the entry Objects >
Configure Object(s) , select the
TriggerTimer_Object from the Declared Object(s) list and click the
Remove
button and Close the dialog again.
We also don't need to set the QML File path
parameter to a fixed value because we can also do this from the
script environment.
- In the Parameters Table View right click and
choose the menu entry Parameters >
Configure Parameter(s) . Select the
Object QML2Viewer_Object and the Block
Block 0: First Block and the Parameter
Global / QML File inside the Parameter
Selection. Now change the parameter value in the
Parameter Configuration using the Value
field:
- Unlock the parameter
and set it to a script reference
"QML2Viewer_Global_QmlFilePath" (without the quotes)
- Click the Update button
We now have a minimal Experiment Structure file that we can use
from inside our custom script.
- Create a new script file using
the menu entry File > New > QtScript
Document
- Save the Script file to the same directory as
were the Experiment Structure file is saved to, using the
key combination Ctrl + s , to a file named
"ImagesAndText_Scripted.qs" (without the quotes).
For running a QML experiment from a custom script file it's
again advised to include a template script that implements a lot of
experimental features for us that we need, allowing us in the
beginning to focus only on fine tuning the experiment. You can
again override everything from the template
script file inside your own custom script or completely write
your own custom script for running a Retinotopy experiment
without making use of the template script. Template script files
are part of the BrainStim Include files, read
this document for more information about Includes and how to
use them. Important: you should never
delete/move or change these Include files! For this example
we'll include a script file named
"BasicQMLExperiment.qs", that internally again includes
another file named "BasicExperiment.qs". Let's write our first
line of code were we'll include the template script:
Include("QtScript/BasicQMLExperiment.qs");
|
- Now that we Included the "BasicQMLExperiment.qs"
template file we have a new script object available which has a
lot of properties that we can use to configure our experiment.
This script object is named BasicExperiment and
is actually first created inside the "BasicExperiment.qs"
template file that is again included by the
"BasicQMLExperiment.qs" template file. The
"BasicQMLExperiment.qs" template file then takes the BasicExperiment object and extends(or
overrides) it with some additional (QML) features which
is now again available for us in our custom script, let's
configure two properties for this script object:
BasicExperiment.sExmlFilePath = BrainStim.getActiveDocumentFileLocation() + "/" + "ImagesAndText_Scripted.exml";
BasicExperiment.nTestModeTriggerDuration = 1000;
|
First we set the file-path for our Experiment structure
that we want to make use of. This file is saved in the same
directory of our custom script file and so we can make use of
the function BrainStim.getActiveDocumentFileLocation() for
retrieving the directory path, see
this document.
After that we'll set the trigger time for a trigger simulation
timer. This is a feature is implemented by our template script,
it implements a TriggerTimer object that automatically
triggers our experiment by invoking incrementExternalTrigger()
slot like we saw in the previous example for us. With the
BasicExperiment.nTestModeTriggerDuration we can
set the trigger time that should be used for our experiment for
a test mode(we'll see later what this test mode exactly does).
We have unlocked a parameters and set them to refer to a
script variable, and now we have to make sure that these exist
in the script, lets declare and give them a initial value like:
var QML2Viewer_Global_QmlFilePath = BrainStim.getActiveDocumentFileLocation() + "/" + "ImagesAndText_Scripted.qml";
|
Now we'll add some additional features to the already created
QML file
- Open the file ImagesAndText.qml file from the
previous example, there's also a solution saved in the
Main Program Directory
under the subfolder
\Examples\BrainStim\QML Basics\ and save it under the name
"ImagesAndText_Scripted.qml" (without the quotes) by using
the menu entry File > Save As...
- Let's add an additional include at the second line so the
first 2 lines look like:
import QtQuick 2.0
import BrainStim_QMLExtensions 1.0
|
This QML include adds some additional features to our QML file of
which we can now make use of, one of these features is the
DebugMode element. This element show the
current experiment structure in the top of our presentation
window while running the experiment. We already saw a similar
looking textual information area for our Retinotopy experiment.
Now we only need to add this element to our QML file, we'll
place it inside a Column element, place the
following code right after the color property
setting of the rootRectangle element:
Column
{
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
DebugMode{}
}
|
Our import made the DebugMode element
available. This element is defined in a file which is also part
of the BrainStim Include files (Include\QML\DebugMode.qml),
read
this document for more information.
- Save the Script file again using the
key combination Ctrl + s
- Let's switch back to our custom script file, open or
activate the file/document ImagesAndText_Scripted.qs.
For this example we'll override a function named
BasicExperiment.NewInitBlockTrial this function is
called exactly at the moment that the Experiment Structure
starts a new Block or Trial. In the previous Retinotopy example
we overridden the BasicExperiment.PrepareNewInitBlockTrial
function because we needed to make sure to set the new script
referenced parameters before they were parsed. This example
we're going to dynamically change the QML scene by invoking one
or more custom functions that we already defined. We'll do that
now by adding the following script code which is very similar
as for the Retinotopy example, but then for the
BasicExperiment.NewInitBlockTrial function:
BasicExperiment.__proto__.NewInitBlockTrial = function()
{
var _currentBlockID = BasicExperiment.nCurrentExperimentStructureState.CurrentBlock_BlockID;
var _currentBlockNumber = BasicExperiment.cExperimentStructure_Object.getBlockPointerByID(_currentBlockID).getBlockNumber();
var _currentTrialNumber = BasicExperiment.nCurrentExperimentStructureState.CurrentBlock_TrialNumber;
BrainStim.write2OutputWindow("*** Going to initialize a new Block-trial: BlockNr: " + _currentBlockNumber + ", TrialNr: " + _currentTrialNumber);
|
Now we
have both the current Block and Trial number we can make use of
them and change the QML scene by invoking a custom function
like:
if(_currentBlockNumber==0)
{
BasicExperiment.InvokeQMLFunction("functions", "setTextFunction", "Block 0");
BasicExperiment.InvokeQMLFunction("functions", "setImageFunction", "images/World. Png");
}
else if(_currentBlockNumber==1)
{
if(_currentTrialNumber==0)
BasicExperiment.InvokeQMLFunction("functions", "setTextFunction", "Block 1, Trial 0");
else
BasicExperiment.InvokeQMLFunction("functions", "setTextFunction", "Block 1, Trial 1");
BasicExperiment.InvokeQMLFunction("functions", "setImageFunction", "images/BrainStim.png");
}
}
|
Here we see a method called
BasicExperiment.InvokeQMLFunction that is automatically
implemented by our BasicQMLExperiment.qs template
include file. When called then this immediately calls the a
QML
function (second parameter) that is defined within a
Item element with a
objectName (first parameter) property set. We can also
provide function arguments (>=third parameter).
In our custom QML functions we used a single string argument,
this is the third argument. We can define up till ten arguments.
- We can again override the function (ExperimentStateChanged)
that is automatically called whenever the experiment state changes.
:
BasicExperiment.__proto__.ExperimentStateChanged = function(currentState, sDateTimeStamp)
{
//This Function is called automatically at each time the Experiment State changes
//See Help documentation (search for "ExperimentManager::ExperimentState") for a description of the different states
if(currentState == ExperimentManager.ExperimentState.ExperimentManager_Initialized)
{
BrainStim.write2OutputWindow("*** ExperimentStateChanged to: Initialized at " + sDateTimeStamp);
}
else if(currentState == ExperimentManager.ExperimentState.ExperimentManager_Started)
{
BrainStim.write2OutputWindow("*** ExperimentStateChanged to: Started at " + sDateTimeStamp);
}
else if(currentState == ExperimentManager.ExperimentState.ExperimentManager_Stopped)
{
BrainStim.write2OutputWindow("*** ExperimentStateChanged to: Stopped at " + sDateTimeStamp);
}
}
|
- Now we have entered enough script code for running the
experiment, which leaves us at the last pieces of code that runs
the experiment:
BasicExperiment.RunExperiment();
|
- Save the Script file again using the
key combination Ctrl + s
- Execute the script file by pressing the green
Execute
button and see what happens.
Editing a QML file with Qt Creator
Qt releases a program called Qt Creator that has an internal QML
editor that let's you edit, view and debug and view QML files. It
can be convenient to use this third party program combined with
BrainStim for the development of an QML file because it has an
WYSIWYG editor, combined with many debugging facilities. One
limitation is that this program needs to support the same version of
the Qt framework as BrainStim for this release you can find a
downloadable installer
from
here
saved under the filename
qt-creator-opensource-windows-x86-3.5.1.exe
- Download the above mentioned installer and install
everything using the default options.
- When everything is successfully installed start the
installed QtCreator (qtcreator.exe) application and open the
file "ImagesAndText_Scripted.qml" (without the quotes) that is located in the
Main Program Directory under the subfolder
\Examples\BrainStim\QML Basics\.
Notice that after opening the log message "plugin
cannot be loaded for module "BrainStim_QMLExtensions"
was appended to the Application Output.
Furthermore the second line from the QML file in the editor
("import BrainStim_QMLExtensions 1.0") has a small thin red line
below it, if we move our mouse over it
will say something
like "QML module not found". The problem here is that
when editing a QML file in QtCreator that it doesn't know about
custom imports (plugins) which are implemented by BrainStim.
Now we need to tell QtCreator from which directory path it can
import these plugins. To do this we need another file, called a
QML project file (*.qmlproject) which we can use for defining
our custom QML plugin path from BrainStim, let's create such a
file in BrainStim (there's also a BrainStim script file that
automatically performs the below steps for you, it's described
after the following example steps):
- Start BrainStim.
- Create a new file using
the menu entry File > New > Undefined Document
- Save this file again using the
key combination Ctrl + s give it a
filename like "ImagesAndText.qmlproject" to the same directory
as the "ImagesAndText_Scripted.qml" file from a previous
example.
- Add the following code inside the new document:
import QmlProject 1.1
Project {
mainFile: "./ImagesAndText_Scripted.qml"
/* Include .qml, .js, and image files from current directory and subdirectories */
QmlFiles {
directory: "."
}
JavaScriptFiles {
directory: "."
}
ImageFiles {
directory: "."
}
/* List of plugin directories passed to QML runtime */
importPaths: [ "C:/Program Files (x86)/BrainStim/Qml/plugins/Win32" ]
}
|
Important!
Make sure to change the importPaths setting
from the above code to match your BrainStim Installation QML
plugin directory path, see
this document.
- Save the file again using the
key combination Ctrl + s and open the file
also with QtCreator.
- If we now open the file "ImagesAndText_Scripted.qml" from
our project we'll see that there are no more error messages as
explained above and we can start using the WYSIWYG editor by
pressing the Design toolbar button and start
editing our QML file using the easy to use UI components.
- We can also now run/execute the QML scene in a window using the
Run
toolbar button or the key combination Ctrl + r
, let's try that.
We can also make use of a small BrainStim script file that
automatically creates a QML project file automatically for us:
- Open in BrainStim the file CreateQtCreatorProject.qs
located in the
Main Program Directory
under the subfolder
\Examples\BrainStim\QML QtCreator\
- Execute the script file by pressing the green
Execute
button
- The script first asks us to browse to the QML file of which
we want to include in the project and hereafter we can save the
QML project to a custom location and under a custom filename.
It's advised to save it to the same directory as where the QML
file is saved. After this the script generates automatically a
QML project file for us to use within QtCreator.
We can also configure BrainStim to execute the QML scene from
within QtCreator, to do this we need to register BrainStim in
QtCreator as an external tool that can execute QML files, let's
try this:
- In QtCreator open the menu entry Tools > External >
Configure...
- Make sure to select Environment from the
left tree view pane and then the External Tools
tab on the right.
Here we can already see which external tools are configured for
editing/executing QML (QtQuick) files.
Depending on the QtQuick import library version used in the QML
file these configured tools are used for executing/running the
QML file. We should always import the latest QtQuick version
2,
like we did in the above examples:
- Click the button Add > Add Tool, rename
the Tool to something like "Qt Quick 2 Preview (BrainStim)"
(without the quotes).
- Complete the
configuration by entering the following settings:
- Description: Executes the current active QtQuick2 document
with BrainStim
- Executable: the file path to the BrainStim
executable, like C:\Program Files
(x86)\BrainStim\BrainStim.exe
- Arguments: the arguments to pass to
the BrainStim executable, we'll pass the QML document file path using
the BrainStim -f (files) and the -e
(execute) command line option (read
this document about BrainStim Command-Line Options) to make
sure that it executes directly: -f
%{CurrentDocument:FilePath} -e
- Working directory: the working
directory, we'll use the documents directory for this: %{CurrentDocument:Path}
- Output: Leave the Show in
Pane option selected
- Error output: Leave the Show in
Pane option selected
- Modifies current document: Leave this setting unchecked
- Input:
Leave this field empty
- Press Apply and OK to close the settings
dialog.
- Test the newly registered external tool by using the
menu entry Tools > External > QtQuick > Qt
Quick 2 Preview (BrainStim). The current active QML file
should now open in BrainStim and automatically execute.