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.

  1. Start BrainStim.
  2. Create a new Experiment Structure file using the menu entry File > New > EXML Document
  3. 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).
  4. Save the Experiment Structure file using the menu entry File > Save to a new empty Experiment Structure file.


  5. 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.

  6. 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.
  7. 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.
  8. 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.
  9. 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 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.
  10. 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).
  11. 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 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:

    void TriggerTimer::startTimer ( double  dMSec ) [slot]

    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.
  12. 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.
  13. 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 Execute button and watch what happens, remember to press Ctrl + a at any time to abort.
  14. 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 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:
  15.  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
  16. 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!
  17. Let's execute our document and see what our above changes lead to. Press the green Execute Execute button
  18. 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.
  19. 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:

  1. Start BrainStim.
  2. Create a new Experiment Structure file using the menu entry File > New > EXML Document
  3. 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).
  4. 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.
  5. 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.
  6. 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.
  7. 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 Execute 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 Execute 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.
  8. 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}
  9. 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 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:
  10. Create a new script file using the menu entry File > New > QtScript Document
  11. 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"); 
  12. 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();
  13. Save the Script file again using the key combination Ctrl + s
  14. 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 Execute button and see what happens.
  15. 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 
       }
    } 

  16. Save the Script file again using the key combination Ctrl + s
  17. Execute the script file by pressing the green Execute Execute button and see what happens.
  18. 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);
       }
    }

  19. Save the Script file again using the key combination Ctrl + s
  20. Execute the script file by pressing the green Execute 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

  1. directly (QML document (*.qml)) from the BrainStim UI by pressing the green Execute Execute button
  2. from a Experiment Structure file (*.exml) with the use  of an declared object from the QML2Viewer class
  3. 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\.

  1. Start BrainStim.
  2. Create a new QML file using the menu entry File > New > QML Document
  3. Save the Script file again using the key combination Ctrl + s give it a filename like "ImagesAndText.qml"
  4. 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 parentkeyword
  5. Save the Script file again using the key combination Ctrl + s
  6. Now we can Execute the script file by pressing the green Execute 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 Execute button and see what happens.
  7. 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.
  8. 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 Execute button and validate what you see is what you would expect from the QML code.
  9. 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 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.
  10.  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 Execute button
  11. 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.

  1. 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... 
  2. Change the Experiment name field the Selection tab of the Parameter List View "Images and Text Experiment" (without the quotes).
  3. Switch to the Graphical Schematic "Objects" View and select the right click menu the entry Objects > Configure Object(s) 
  4. Select the RetinoMapper_Object from the Declared Object(s) list and click the Remove button.
  5. Add a new object from the QML2Viewer class and rename it to  "QML2Viewer_Object" (without the quotes) and close the dialog.
  6. 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.
  7. 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 4 1
  8. We again know that there are parameters that can be configured for the "QML2Viewer_Object" object because of the presence of the parameters 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
  9. Now it's again time to execute our document and see what our above changes lead to. Press the green Execute 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:

  1. 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. 
  2. 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.
  3. 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 Execute 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.
  4. Create a new script file using the menu entry File > New > QtScript Document
  5. 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"); 
  6. 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
  7. 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... 
  8. 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.
  9. Save the Script file again using the key combination Ctrl + s
  10. 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.
  11. 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);
       }
    }

  12. 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();
  13. Save the Script file again using the key combination Ctrl + s
  14. Execute the script file by pressing the green Execute 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

  1. Download the above mentioned installer and install everything using the default options.
  2. 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):

 

  1. Start BrainStim.
  2. Create a new file using the menu entry File > New > Undefined Document
  3. 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.
  4. 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.
  5. Save the file again using the key combination Ctrl + s and open the file also with QtCreator.
  6. 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.
  7. 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:

  1. Open in BrainStim the file CreateQtCreatorProject.qs located in the Main Program Directory under the subfolder \Examples\BrainStim\QML QtCreator\
  2. Execute the script file by pressing the green Execute Execute button
  3. 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:

  1. In QtCreator open the menu entry Tools > External > Configure...
  2. 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:

    import QtQuick 2.0

  3. Click the button Add > Add Tool, rename the Tool to something like "Qt Quick 2 Preview (BrainStim)" (without the quotes).
  4. 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
  5. Press Apply and OK to close the settings dialog.
  6. Test the newly registered external tool by using the menu entry ToolsExternalQtQuickQt Quick 2 Preview (BrainStim). The current active QML file should now open in BrainStim and automatically execute.