-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
366 lines (326 loc) · 50.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Bassoon</title>
<style>
body {
background-color: #fcfcfc;
color: #000000;
margin-left: 15%;
margin-right: 15%;
text-align: justify;
}
.button-container {
display: flex;
flex-direction: row;
align-items: center;
/* Aligns items vertically */
justify-content: center;
margin: 0 auto
}
button {
border-radius: 10px;
background-color: rgb(255, 255, 255);
margin: 5px;
color: #000000;
}
button:hover {
background-color: rgb(4, 132, 0);
color: #fcfcfc;
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
}
h2{
padding-top: 30px;
}
.center {
margin: auto;
width: 50%;
padding: 10px;
}
.codeHighlight{
color:rgb(13, 191, 64);
}
.codeComment{
color:rgb(100, 100, 100);
}
img {
padding-top: 20px;
padding-bottom: 20px;
height: 400px;
display: block;
margin: 0 auto;
}
code{
background-color: rgb(215, 215, 215);
border-radius: 3px;
color: #b92b64;
}
#toc_container {
background: #f9f9f9 none repeat scroll 0 0;
border: 1px solid #aaa;
display: table;
font-size: 100%;
margin-bottom: 1em;
padding: 20px;
width: auto;
}
.toc_title {
font-weight: 700;
text-align: center;
}
#toc_container li, #toc_container ul, #toc_container ul li{
list-style: outside none none !important;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
setRepoButtonHeight()
setBackgroundColor()
//add more listeners only after the DOM is loaded:
window.addEventListener('resize', function () {
setRepoButtonHeight();
});
});
// set the height of the repo button to match the height of the download button
function setRepoButtonHeight() {
var downloadButton = document.getElementById("downloadButton");
var repoButton = document.getElementById("repoButton");
var downloadButtonHeight = window.getComputedStyle(downloadButton).height;
repoButton.style.height = downloadButtonHeight;
}
// set the background color of the page to a random pastel
function setBackgroundColor() {
var color1 = Math.random() * 0.05 + 0.95;
var color2 = Math.random() * 0.05 + 0.95;
var color3 = Math.random() * 0.05 + 0.95;
color1 = Math.floor(color1 * 255);
color2 = Math.floor(color2 * 255);
color3 = Math.floor(color3 * 255);
var body = document.body
body.style.backgroundColor = 'rgb(' + color1 + ',' + color2 + ',' + color3 + ')';
}
</script>
</head>
<body>
<h1 style="text-align: center; color:rgb(4, 132, 0)">Bassoon</h1>
<img src="help/images/bassoonPlayer.png">
<p style="text-align:center"><b><em>Develop, organize, and deploy vision science experiments.</b></em></p>
<p>Bassoon is no-code visual graphics software to easily create, manage, and execute custom visual stimuli for vision science experiments. Bassoon is python based, and is built on the popular <a href='https://www.psychopy.org' target="_blank">Psychopy</a> API.
</p>
<p>Maintained by the <a href="https://dunnlab.ucsf.edu" target ="_blank">Dunn Lab</a> at UCSF.</p>
<div class="button-container" style="width:60%; margin-top: 30px; margin-bottom: 30px;">
<a href="https://github.com/ScottHarris17/Bassoon/archive/refs/heads/main.zip">
<button id="downloadButton">
<span>
<svg aria-hidden="true" focusable="false" role="img" viewBox="0 0 16 16" width="16" height="16"
fill="currentColor"
style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;">
<path
d="M3.5 1.75v11.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 2 13.25V1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.185 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 12.25 15h-.5a.75.75 0 0 1 0-1.5h.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177L9.513 1.573a.25.25 0 0 0-.177-.073H7.25a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5h-3a.25.25 0 0 0-.25.25Zm3.75 8.75h.5c.966 0 1.75.784 1.75 1.75v3a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1-.75-.75v-3c0-.966.784-1.75 1.75-1.75ZM6 5.25a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 6 5.25Zm.75 2.25h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 6.75A.75.75 0 0 1 8.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 6.75ZM8.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 9.75A.75.75 0 0 1 8.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 9.75Zm-1 2.5v2.25h1v-2.25a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25Z">
<!--Draws the little zipper icon on the button-->
</svg>
</span>
<div><span><b>DOWNLOAD ZIP</b></span></div>
</button>
</a>
<a href="https://github.com/ScottHarris17/Bassoon" target="_blank"><button id="repoButton"><b>View Repository</b>
</button>
</a>
</div>
<h1>Documentation</h1>
<div id="toc_container">
<h3 class="toc_title">Table of Contents</h3>
<ul class="toc_list">
<li><a href="#requirementsAnchor">Requirements</a></li>
<li><a href="#installationAnchor">Installation</a></li>
<ul>
<a href="#commonProblemsAnchor">Common Problems</a>
</ul>
<li><a href="#generalUseAnchor">General Use</a></li>
<li><a href="#NutsAndBoltsAnchor">Nuts And Bolts</a></li>
<ul>
<li><a href="#epeAnchor">Experiments, Protocols, and Epochs</a></li>
<li><a href="#mainpyAnchor">main.py</a></li>
</ul>
<li><a href="#useAnchor">Use and Contributions</a></li>
</ul>
</div>
<h2 id="requirementsAnchor">Requirements</h2>
<h5>Critical</h5>
<ul>
<li>Windows (10+) or Linux (Bassoon has not been validated on macOS, iOS, Android, or Linux distributions other than Ubuntu).</li>
</ul>
<h5>Recommended</h5>
<ul>
<li>A modern graphics card. While Bassoon will run on a CPU, optimal performance during experiments is achieved only with a GPU.</li>
<li>>1 monitor. Typically, it is helpful to play stimuli on one monitor and to show informational text about stimuli on a second.</li>
</ul>
<h5>Optional</h5>
<ul>
<li>A USB to TTL serial cable can be used to output precise timing data from the computer running Bassoon to another device. This is typically used to align stimuli to data collected in another program or on another machine (e.g., for electrophysiology or behavioral experiments).</li>
<li><a href = "https://paulbourke.net/dome/meshmapper/" target = "_blank">Meshmapper</a> can be used to create morph files that Bassoon uses to project stimuli onto spherical displays instead of planar monitors.</li>
</ul>
<h2 id="installationAnchor">Installation</h2>
<p> Bassoon relies on <a href="https://www.python.org/downloads/" target = "_blank">Python 3</a> and the <a href='https://www.psychopy.org' target="_blank">Psychopy</a> libraries. Follow these steps to install Bassoon on your computer:
<ol>
<li><u>Download Bassoon:</u> Download a zipped Bassoon folder <a href="https://github.com/ScottHarris17/Bassoon/archive/refs/heads/main.zip">here</a>. You can also download or clone directly from the <a href="https://github.com/ScottHarris17/Bassoon" target="_blank">Github repository.</a> Once downloaded, extract the files into a directory on your local computer (if you'd like to receive updates to your local copy as they're published to github, cloning is highly recommended over downloading. If you're new to git and using Windows, checkout <a href = "https://desktop.github.com/download/" target="_blank">github desktop</a> to manage your packages).</li>
<li><u>Download Python 3, Create an Environment, and Install Psychopy:</u> There are two options for downloading python if you do not already have it.
<ol>
<li><em>Create a conda environment (recommended):</em> Download <a href="https://www.anaconda.com/download" target="_blank">anaconda</a> or <a href="https://docs.anaconda.com/miniconda/" target="_blank">miniconda</a>. Launch the terminal (on windows, you must use "Anaconda Prompt") and create a new conda environment called "Bassoon" by typing <code>conda create -n Bassoon python=3.10</code>. Once the environment is created, activate it using <code>conda activate Bassoon</code> and install psychopy by entering <code>pip install psychopy</code> (note that there used to be a conda distribution for Psychopy, but it is no longer up to date). If you run into trouble, look through the <a href="https://www.psychopy.org/download.html" target="_blank">Psychopy documentation</a>.</li>
<li><em>Install python/psychopy outside of conda:</em> If you would prefer to install python/psychopy in a different way, you may. Regular Venvs should work along with pip installs. Be sure to use <a href="https://www.python.org/downloads/" target = "_blank">python 3</a> and to pay close attention to the <a href="https://www.psychopy.org/download.html" target="_blank">psychopy documentation</a>.</li>
</ol>
<li><u>Install Additional Dependencies:</u> Other packages that are needed are tkinter and pyserial. Install them into your conda or virtual environment by running <code>pip install tk</code> and <code>pip install pyserial</code>. Make sure that your conda or virtual environment is activated before pip installing (typically, this is indicated by the name of the environment being listed in parentheses in your terminal/anaconda prompt. For instnace, <code style="background-color: #000000; color:#fcfcfc">(base) C:\Users\mrsco></code> indicates that the base environment is active). </li>
<li><u>Install an IDE for Python:</u> This step is not strictly required, but highly recommended. It will allow you to make changes to Bassoon, including adding or modifying stimuli on your local device, debugging errors, and making contributions to the Github repository. If you installed Psychopy via conda, make sure the environment is activated (<code>conda activate Bassoon</code>) and then enter <code>conda install spyder</code> into the terminal (or conda prompt). Once installed, you can type <code>spyder</code> into the terminal, and it should launch an IDE from which you can view, test, and run Bassoon. If you prefer an IDE other than spyder, a popular option is <a href="https://code.visualstudio.com/" target = "_blank">VS Code</a> (you can follow <a href = "https://code.visualstudio.com/docs/python/python-tutorial" target = "_blank">these instructions</a> on how to connect VS code to python, especially if you are not using a conda environment).</li>
</ol>
You should now be ready to run Bassoon. Locate the file called <em>main.py</em>, which is located in /Bassoon/src/. Either open this file in your IDE and press run, or run it directly from your terminal (on Windows: <code>python /path/to/main.py</code>). If the Bassoon window launches then you're ready to go! If you encounter an error (either here or at any further step) make sure you have the neccessary dependencies installed.
</p>
<h5 id="commonProblemsAnchor">Common Problems</h5>
<p>This section contains a list of known errors that users have encountered while trying to install Bassoon.
<ul>
<li><em>Package Versions:</em> pip installing psychopy should give you the most up to date version with no dependency conflicts. If you run into errors installing or using the manually installed packages (tk and pyserial) try explicitly installing the following versions:
<ul style="list-style-type: none;">
<li><code>pip install tk==8</code></li>
<li><code>pip install pyserial==3.5</code></li>
</ul>
<li><em>Dukpy Error:</em> Some users have encountered a problem installing the Dukpy package when running <em>pip install psychopy</em>. Typically the error reads something like <code style="background-color: #000000; color:#fcfcfc"><span style="color: #b92b64;">ERROR: </span>Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools"</code>. The solution is to download and install <a href="https://visualstudio.microsoft.com/" target="_blank">Visual Studio</a> from Microsoft, making sure to select the C++ packages during the installation process. Sometimes a link to dowload the software is also reported with the error message, which works equally well. Once you've installed Visual Studio, try rerunning <code>pip install psychopy</code>.</li>
<li><em>macOS Fatal Python Error:</em> Bassoon can be successfully installed on Apple Mac following the same steps described above. However, when trying to launch the GUI by running the main.py file, several users have encountered a fatal python error. This seems to be an inherent problem with how Macs use Tkinter, and a quick google search reveals it's a common problem across many applications. There is no known solution to this issue yet; thus, it may not be possible to run the Bassoon GUI on Mac. However, experiments <a href = "#programmaticAnchor">can still be created programmatically,</a> and all of Bassoon's core functionality still works.</li>
</ul></p>
<p><b>General Advice for Python Beginners:</b> If you're new to programming, one of the most tempting things to do when you encounter a new or unknown error is to give up and ask for help. However, a closely kept secret is that even for experienced programmers, almost every error is a new error! I strongly encourage you to try to debug errors and problems yourself (at least for a bit). It's by far the best way to learn and build confidence. The biggest tips I can provide are to <em><u>read the error messages and terminal outputs, look through the code (especially if an error message points you to a particular line), experiment, and USE GOOGLE!</u></em> There's a 99% chance that the solution is documented somewhere online - Stack Overflow, Reddit, and even ChatGPT are great starting points. If you really can't figure something out, then ask for help - but don't be surprised if the "expert" you turn to just as quickly goes to Google to find the answer.</p>
<h2 id="generalUseAnchor">General Use</h2>
<p>This section describes how to create custom experiments using Bassoon's no-code GUI. For a detailed explanation of how Bassoon operates behind the scenes, including descriptions on how to create new protocol classes, see <a href="#NutsAndBoltsAnchor">Nuts And Bolts</a>.</p>
<p>To launch Bassoon run the main.py file. Enter <code>python /path/to/main.py</code> in your terminal, where <em>/path/to/</em> is replaced by the path on your local machine to the directory that houses Bassoon's main.py file (you can also <em>cd</em> to the directory and then just enter <code>python main.py</code>).</p>
<p>The following window will open <img src=help/images/bassoonMainWindow.png alt="Bassoon Main Window With Labeled Buttons" style="width:300px; height: 550px;"></p>
<p>The general work flow is to create (or load) an "experiment sketch" and then run it. The experiment sketch consists of a list (in order) of all the stimuli that you will run, customizations that you have made to each of them, and the settings that you would like to use for the experiment. Once you've built an experiment sketch, you can run it and save it. You can also load previously built experiment sketches directly into Bassoon, which can then be run immediately.</p>
<p>The red numbers in the image above label the basic buttons to use in Bassoon. Broadly, you will add stimuli to your experiment sketch, customize their parameters, customize the experiment settings, and then run the experiment. Below is specific information on each button:
<ol>
<li>Protocol Drop Down Menu: Click this to see all available stimuli that you can add to your experiment. Select one to add.</li>
<li>Protocol Index Box: Enter the index number where you would like to add your selected protocol. 1 corresponds to the first protocol that will be played in the experiment sketch.</li>
<li>Add Stimulus Button: Press this button to add the stimulus you selected at the index number listed in the Protocol Index Box.</li>
<li>Protocol List: After pressing the Add Stimulus button, your new stimulus should be displayed in the white area. The order of stimuli listed here corresponds to the order in which they will play. There are several features available via the "Quick Actions" menu (number 10) to modify this list rapidly.</li>
<li>Edit Stimulus Parameters: Bassoon allows you to easily customize the parameters of each stimuli in the experiment sketch. Select any of the stimuli in the protocol list, either by single clicking on their name and then pressing edit button (5b), or by double clicking on their name. This should open a new window, which shows all of the customizable parameters for the stimulus. <img class="screenshot" src=help/images/stimulusParameters.png alt="Parameters for the flash stimulus" style="width:390px; height:360px"> You can change the values of each parameter using the text boxes next to their name in the new window. The information in brackets < > on the right side tell you the data type that you must enter (these correspond to python classes). For lists, you'll see two entries, one that says 'list' and a second that specifies the data type of each list element, such as 'float'. You can click the information buttons next to each parameter to print out a description of what they do to the console. Once you've customized the stimulus by changing all necessary parameters, click the Apply Changes button. You should receive a report in your python console indicating that the properties were successfully updated. After pressing Apply, the Estimated Time value will also update, indicating the amount of time that the stimulus is expected to take. Once you are finished customizing the parameters, you may exit the edit window.<em> Note that for lists, you must use the correct syntax, including square brackets on either end and commas between entries. If you enter the wrong data type, your changes may be rejected, and this will be reflected in the python console.</em>
<br/> <br/>
While most stimuli share some parameters in common, each one also contains its own unique parameters. If the information icons do not provide enough information, the best way to get familiar with what each parameter does is through experimentation. You can also look at the source code files (see <a href="#NutsAndBoltsAnchor">Nuts and Bolts</a>). A few common stimulus paramaters are described here:
<ul>
<li>backgroundColor: The color of the background of the monitor, behind/under the presented stimulus. Like all parameters that modify a color property, this value is given as a list of floats corresponding to RGB values. A value of -1.0 corresponds to 0 (dark/black) and a value of 1.0 corresponds to 255 (bright/white).</li>
<li>interStimulusInterval: The time between epochs, given in seconds</li>
<li>preTime: The time at the beginning of a stimulus before the stimulus is made dynamic. For the Flash stimulus, this is a period of time in which the background is visible, but the flash has not started. For an oscillating grating stimulus, the grating is visible during this time, but it hasn't started oscillating yet. Given in seconds.</li>
<li>protocolName: The name of the protocol that you are playing. This defaults to the corresponding file name in Bassoon, and changing this is usually not recommended.</li>
<li>randomSeed: A random float value between 0 and 1 that is used for pseudorandom computations in some stimuli. For instance, in a stimulus that presents a random sequence of images, the random seed can be used to calculate the sequence to show the images. By using the same random seed twice, you can exactly repeat the same pseudorandom sequence.</li>
<li>stimTime: During this time, the stimulus becomes dynamic. For the flash, it's the amount of time that the screen lights up. For the oscillating grating, it's the amount of time that the grating oscillates. Given in seconds.</li>
<li>stimulusReps: Number of repetitions of the stimulus. For some stimuli, this number is multiplied by another parameter (such as a list of directions for a moving bar) to determine the total number of epochs. For the flash, the number of epochs is equal to the number of stimulusReps. Given as an integer.</li>
<li>suffix: A string value that you can add on to the name of your stimulus in order to differentiate its display name from other similar ones. For instance, you might add "_long" to the flash, which will update its name in the protocol list to "Flash_long". It's always recommended to start suffix strings with the underscore character "_" in order to distinguish them from the protocolName.</li>
<li>tagList: A list of strings that you can use to add tags to a stimulus for analysis. Add any tags you want.</li>
<li>tailTime: The same as the preTime, but it happens after the stimTime instead of before.</li>
<li>userInitiated: A boolean value that is set to True only if the user wants to manually press a keyboard button to specifically start the stimulus during the experiment. This value can also be set for all stimuli at once in the experiment settings.</li>
</ul>
</li>
<li>Remove Button: Select a stimulus and then press this button to delete it from the experiment sketch.</li>
<li>Save Button: You can save an experiment sketch at any time using this button. When a stimulus runs, new parameters are generated so it's always important to save after running (and Bassoon will automatically prompt you to do this by openning a save dialogue at the end of each experiment). However, you can also save an experiment before running it (including while you're in the process of designing it). This allows you to build an experiment ahead of time, save it, and then reload the experiment into Bassoon when you're ready to run it. Similarly, you can rerun previous experiments that you've performed. The button appears red when the current experiment is not saved and green when it is saved.</li>
<li>Load Experiment Menu: Select this button to load a previous experiment. You will be taken to a dialogue box to select a previosuly saved experiment. Once loaded, that experiment sketch will automatically populate your Bassoon window. You can run it immediately, or make additional customizations. Note, that when loading experiments, you will load in the same randomSeed parameters as previously used, meaning that pseudorandom stimulus sequences will be identical unless this value is changed.</li>
<li>Options Menu: Allows you to customize specific parameters for your experiment. See <a href="#experimentParametersAnchor">below</a> for a walkthrough of some of these features. Of primary importance, <em>you must set up a monitor using the <a href="https://psychopy.org/api/monitors.html" target="_blank">Psychopy monitor API</a> or <a href="https://www.psychopy.org/general/monitors.html">monitor center</a> in order for stimuli to appear at the correct size during experiments.</em><img src=help/images/experimentOptions.png alt="Experiment Options Window" style="width:400px;height:440px;"></li>
<li>Quick Actions Menu: Use this window to quickly perform different tasks. You can reorder stimuli, make a copy of a stimulus you have already customized, or manually turn ON and OFF a TTL output (if you have one).<img src=help/images/quickActionsMenu.png alt="Quick Actions Menu" style="width:200px;height:480px;"></li>
<li>Run Experiment Button: Press this button once you have fully customized your experiment. It will launch the stimuli in a new window on the screen you specified in the Options Menu (#9). The stimuli will play in the order that they appeared in the protocol list box. The main Bassoon window will disappear from view while stimuli are playing, but Bassoon will continue to send you real time updates about the experiment through the python console, so look for messages. You may skip a stimulus at any time by pressing the 'q' key. You may also pause a stimulus by pressing the 'p' key. If you have selected to use an information window, additional information about the stimulus will appear there. Once the stimulus is done playing, you will automatically be requested to save the experiment. It is a good idea to do so, even if it has been previously saved, because there are new parameters that are created when the experiment runs. These include many paramters about the structure and timing of stimuli that can be critical for analysis.</li>
<li>Quit Menu Option: Once you have finished running your experiment and saved everything, you may exit Bassoon by pressing the quit button.</li>
</ol>
</p>
<p>
When Bassoon saves experiments, it creates one file with the extension .EXPERIMENT, and another that is in JSON format. The former is a pickled python file that can be easily loaded into python for analysis. The latter contains all of the same data, but is visualizable and more easily used with other programming languages such as MATLAB or R. All of the parameters from each protocol that you added to the experiment are available through either file.
</p>
<h2 id="NutsAndBoltsAnchor">Nuts and Bolts</h2>
<h3 id= "epeAnchor">Experiments, Protocols, and Epochs</h3>
<p>
Bassoon's goal is to present a temporally precise sequence of customized stimuli to the user. It does this by organizing stimuli into three levels of abstraction. From lowest to highest level:
<ul>
<li><u>Epochs:</u> An epoch is a single itteration of a stimulus. For a stimulus that consists of a bar that drifts across the screen for instance, an epoch equates to one pass of the bar across the screen. For a flash stimulus, an epoch contains one flash</li>
<li><u>Protocols:</u> A protocol is a type of stimulus. Examples of protocols include the Drifting Bar, Oscillating Grating, and Flash. Protocols typically consist of multiple epochs (unless the user specifies that only a single epoch should be played).
<ul>
<li>Each protocol has a .py file associated with it in <em>Basson/src/protocols</em>. This file defines the stimulus, its parameters, and what happens when it is run. There is a common set of functions that each protocol contains, including <code>__init__()</code>, <code>estimateTime()</code>, and <code>run()</code>:
<ul>
<li><code>__init()__</code>: Initializes the protocol (i.e., is executed when the protocol object is created by Bassoon - specifically, this happens when the user adds the protocol to their experiment sketch using the "Add Stimulus" button in the GUI). Listed at the top of this function are several attributes. Some are common across stimuli, while others are particular to certain stimuli. When a user "customizes" a stimulus, they are modifying these attributes. Any attribute that starts with an underscore "_" charcter, however, is not customizable by the user (e.g., <code>self._angleOffset</code>). These underscore properties are manipulated programmatically by Bassoon.</li>
<li><code>estimateTime()</code>: A function that approximates the total time that the stimulus will take to run, based on key properties. The mechanics of this function are similar across protocols, but vary according to the specifics of how the protocol's <code>run()</code> function is designed.</li>
<li><code>run()</code>: This is where Bassoon calls on the psychopy libraries to build and execute stimuli. The first half of the function typically involves creating the initial state of the stimulus and performing some computations that will be used to dynamically modify the stimulus during the stimTime. The <code>run()</code> function almost always also contains a for-loop, which iterates through each epoch to sequentially present them. In the loop, the psychopy stimulus is repeatedly updated and pushed to the stimulus monitor on every frame. This update step typically involves updating some parameter of the stimulus to make it dynamic across time (e.g., changing the position of a drifting bar stimulus on each frame such that it glides across the screen). For every epoch, nearly all protocols have 4 distinct sections of the for-loop that execute:
<ul>
<li>Interstimulus Interval: The period of time between sequential epochs. Typically, a blank or gray screen is presented during this time</li>
<li>Pre Time: This is a static period that occurs immediately before a stimulus starts. It typically consists of a static version of the stimulus presented on the screen (e.g., a stationary grating during the oscillating grating stimulus).</li>
<li>Stim Time: The stimulus is dynamic during this time (e.g., an oscillating grating is continuously oscillating).</li>
<li>Tail Time: Very similar to the pretime, but occurs after the stim time </li>
</ul>
Bassoon runs according strictly to frames, not time. This means that for each of the 4 sections of the for-loop above, there is a specified number of frames that Bassoon must execute (which are typically calculated during the first part of the <code>run()</code> function, before the loop). Thus, stimuli that are computationally/graphically heavy can slow down the execution. If this occurs, it will be documented in several saved parameters that are created during the execution of the stimulus, such as <code>self._stimulusStartLog</code> and <code>self._stimulusEndLog</code>, which save the start and end time of each epoch. Note that these attributes start with the underscore "_" character. As described above, that makes them (and many similar ones like them that are created in the <code>run()</code> and other functions noneditable by the user. They are, however, saved with the stimulus so they can be used for analysis.</li></li>
</ul></li>
<li> Each protocol file is also a subclass of the protocol superclass (located in the same directory), which holds an additional sete of common functions and parameters that are inherited by all protocols:
<ul>
<li><code>__init__()</code>: Same as the corresponding function for each individual protocol, but these attributes are inherited across all protocols (i.e., in addition to the attributes that are specified within each protocol's own <code>__init__()</code> function).</li>
<li><code>validatePropertyValues()</code>: Used to check whether the user has entered valid values for editable attributes when an experiment is being created. Several individual protocols also have functions called <code>internalValidation()</code>: which is where the validation is actually done. But, this is a newer feature, and because not every protocol has an <code>internalValidation()</code> function, this <code>validatePropertyValues()</code> function serves as a generally callable function to handle validations. When making new stimuli, it's a good idea to create an <code>internalValidation()</code> function in the protocol subclass. See the MovingGratingScotoma.py file for an example of how to write a validation function (and you can validate any and all parameters that you like!).</li>
<li><code>printDescription(attributeName)</code>: Called when a user presses the information button for a parameter in the edit protocol window. This function prints the description of that parameter to the console. Specifically, the description is defined by the comment in the <code>__init__()</code> function in the protocol's .py file, or in the protocol.py superclass.</li>
<li><code>_get_attribute_descriptions(cls)</code>: A helper function that is called by <code>self.printDescription(attributeName)</code>, this function traverses the protocol's <code>__init__()</code> function to extract the informational comments next to each attribute. It also caches the results so that the process only needs to be performed once per protocol.</li>
<li><code>getFR(win)</code>: Returns the frame rate of the stimulus monitor (<em>win</em>) and calculates the number of frames needed for the pretime, stimtime, and tailtime. This function is usually called near the top of each protocol's <code>run()</code> function. Note, that because this is an inherited function by all protocols, they all must have a pretime, stimtime, and tailtime field (sometimes regardless of if they're used). The MovingGratingScotoma stimulus, for instance, should not have a user editable stim time based on its mechanics. However, it needs an attribute called <code>self.stimTime</code> to avoid raising an exception in this and other functions. To get around this, the stimtime is checked in the <code>internalValidation()</code> function and reassigned to 0 if the user attempts to change it. A notice is also provided to the user through a print statement in the console.</li>
<li><code>getPixPerDeg(stimMonitor)</code>: Calculates the number of pixels per visual degree for the stimulus monitor. To do this, the function draws on the attributes of the saved/loaded monitor that the user has indicated that they are using (through the attributes of the experiment object). Before running Bassoon for the first time, you may want to set up (and save!) a new <a href = "https://psychopy.org/api/monitors.html" target = "_blank">psychopy monitor</a>. This can be done entirely through the psychopy api (outside of Bassoon) and/or in the psychopy GUI through the <a href ="https://www.psychopy.org/general/monitors.html" target="_blank">monitor center</a>. If you have saved your monitor correctly, it should show up as a selectable option in the dropdown menu under "monitors" in Bassoon's "options" menu (accessible through the GUI).</li>
<li><code>showInformationText(stimWin, txt)</code>: Pushes a message to the information window if the user has enabled it. This is typically updated for each epoch (i.e., for each iteration of a protocol's <code>run()</code> function.</li>
<li><code>checkQuitOrPause()</code>: Checks for two keystrokes that the user can execute while a stimulus is running: <kbd>p</kbd> pauses a stimulus and <kbd>q</kbd> quits a stimulus (jumps to the next one in the experiment, or ends the experiment if it's the last one). Both pauses and quits are documented in saved parameters.</li>
<li><code>sendTTL()</code>: This function is used to send timing signals over a TTL port. This is useful if you want to align the stimulus to something outside of Bassoon (e.g., a recording of animal behavior). Enable a port from the options menu in the GUI. Hardware wise, you can simply use a USB-to-TTL serial cable such as <a href = "https://www.amazon.com/ADAFRUIT-Industries-954-Serial-Raspberry/dp/B00DJUHGHI?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A2GTSJRNFEVVSP" target = "_blank">this one</a> and the system should recognize it once plugged in. Bassoon sends TTL pulses via the RTS pinout. There are two different TTL writing modes that provide different levels of precision:
<ul>
<li>Pulse: Writes one byte rapidly over the line. Used for high temporal precision experiments (such as electrophysiology recordings), but is more difficult to decode in analysis (though certainly not impossible).</li>
<li>Sustained: Holds the RTS pinout at 5V during each epoch and 0V between epochs/stimuli. This level of timing is sufficient for most behavioral experiments where millisecond precision is not critical (including eye tracking experiments).</li>
</ul></li>
To better understand how each stimulus uses TTL pulses to send timing signals, look at the their individual files. The calls to <code>sendTTL()</code> happens inside the for-loop in the <code>run()</code> function.</ul></li>
<li><code>burstTTL(win)</code>: Sends a stereptyped burst of TTL pulses when in Pulse mode in order to mark the start of a stimulus. This is only implemented for a few stimuli. This function can be modified to change the signature burst to be anything you like, but currently it sends 20 TTL pulses at frame rate, pauses for 0.2 seconds, and then sends 20 more TTL pulses at frame rate. Note: in some contexts, it can be critical to not send TTL pulses faster than the frame rate because depending on the temporal resolution of your detection system, pulses can be missed.</li>
<li><code>reportTime(displayName)</code>: Prints a timing report for each protocol to the console when the user checks the corresponding box in the options menu of the GUI. This can be very helpful to understand whether a stimulus is too computationally/graphically heavy to execute at frame rate. It's recommended that each stimulus is tested and timing reports are printed prior to using them in actual experiments.</li>
<li><code>printProgressBar(iteration, total)</code>: Used to print a progress bar to the console when needed. This is typically only used for stimuli that have a lot of upfront computation to perform (usually in the <code>run()</code> function) prior to executing in order to inform the user as to approximately how long it will take to load the stimulus.</li>
</ul></li>
<li><u>Experiments:</u> Experiments are a custom class in Bassoon that contain a list of all the protocols that the user wants to run. You can think of an experiment as a large bag. As a user creates an experiment, they add protocols (with all of their properties) to the bag. When the experiment is run, those protocols are removed from the experiment bag one by one and executed for the user.
<ul>
<li>Loading Experiments: Only one experiment can be loaded into Bassoon at a time. You can do this either by launching the GUI and adding protocols using the drop down menu, or by loading a previously run experiment using the "load experiment" option in the menu bar. When an experiment is loaded, all of the properties are loaded, as well as the list of protocols it contained, and the properties of those protocols. Thus, you can design an experiment well ahead of running it, save it, and then load it when needed. If you are running the same experiment on several subjects, you can always load the same experiment file!</li>
<li id="experimentParametersAnchor">Experiment Properties: Experiment properties hold true across stimuli. Thus, these are things like which monitor to use, whether you would like to use a timing signal, and so forth. Properties can be edited (and saved) through the options menu in the GUI. If you save properties, they will be loaded the next time you launch Bassoon. Most properties are intuitive, but here are a few that deserve some explanation:</li>
<ul>
<li>Monitor: Select which monitor to use for the stimulus. NOTE: A MONITOR IS NOT EQUIVALENT TO A SCREEN. A monitor object in psychopy defines the parameters of the monitor (e.g., size and how far the user is positioned from the screen), and is used to adjust the size of stimuli accordingly. The monitor can theoretically be used on any screen, though it should really only be used with the appropriate one. The dropdown menu in the options menu shows all available monitors that you have saved through psychopy. You will likely want to create and save a new monitor prior to using Bassoon beyond testing. You can do this <a href = "https://psychopy.org/api/monitors.html" target = "_blank"> through the psychopy API</a> or in the psychopy GUI through the <a href ="https://www.psychopy.org/general/monitors.html" target="_blank">monitor center</a>. If you have saved your monitor correctly, it should show up as a selectable option in the dropdown menu under "monitors".</li>
<li>Full Screen: Check the box to load the stimulus in full screen mode. WARNING: While full screen mode is typically used for real experiments, be careful when using it during experiment creation and debugging. It can be exceedingly diffuclt to manually exit out of psychopy windows once they are created, so if python or Bassoon crashes while a psychopy window is open in full screen mode, you may be forced to make a hard restart.</li>
<li>Screen #: Select the screen number that you would like to present the stimulus on. This is 0-indexed according to how your system orders your screens.</li>
<li>Use Information Window?: Check the box to show a second window during the experiment, which typically displays text with information about the current stimulus. It can be helpful to place this information window on a second monitor for only the experimenter to see (not using full screen mode). It's usually not critical to create a psychopy monitor for the information window since subjects are not typically looking at it.</li>
<li>Manually Initiate Each Protocol: When checked, the user must press a button before the start of each stimulus in order for it to begin. Selecting this option WILL NOT update stimuli that are already in the experiment list. So, if you switch this value after already having added stimuli to your experiment, be sure to go into each pre-existing stimulus to make sure the "userInitiated" setting is what you want it to be. (Note: this behavior can be leveraged for efficiency, for instance by first adding all stimuli that you don't want to manually initiate, and then checking this option and subsequently adding all stimuli that you do want to manually initiate).</li>
<li>Angle Offset: Applies a constant angular offset to all stimuli in the experiment.</li>
<li>Write TTL Pulses: Set the TTL timing signals to None, Pulse, or Sustained (see above). When in None, timing signals will not be executed.</li>
<li>TTL Port: Select the port name to use to send timing signals. The port typically must be configured as a serial/TTL port (see above).</li>
<li>TTL Bookmarks: Sends specific timing signal signatures at the start and stop of stimuli when TTL mode is set to sustained. See the protocol.py file to understand how this is used.</li>
<li>Use FBO?: Check this box when you want to use a frame buffer object, which is needed to morph stimuli for projection on curved surfaces.</li>
<li>Warp File Location: Enter the path to the warp file when using FBO. If no path is entered, but the Use FBO box is checked, you may encounter an error. Warp files can be generated using <a href = "https://paulbourke.net/dome/meshmapper/" target = "_blank">meshmapper</a>.</li>
<li>Recompile Experiment When Saving: There's almost never a need to check this box outside of development. It overwrites custom properties set for each stimulus when saving. Recompilation is automatically turned off when a user is saving an experiment immediately following a run.</li>
<li>Print Timing Reports: Prints timing information to the console after each stimulus. Use this when determining if stimuli that you've created can run at the proper speed.</li>
</ul>
The underlying experiment attributes that are manipulated can be found in the experiment.py <code>__init__()</code> function. There are also several other attributes that are not accessible to the user through the options menu.
<li>The experiment class has a few methods:
<ul>
<li><code>__init__()</code>: Establishes the default attributes of the experiment when its first loaded (which happens when Bassoon launches). If the user has previously saved experiment properties, it will load this from a JSON file.</li>
<li><code>addProtocol(newProtocol):</code> This function adds protocols to the experiment as their created. Using the analogy above, this is what runs when you place a new protocol into your experiment bag.</li>
<li><code>establishPort(portInfo)</code>: Handles the TTL port that is used to send timing signals. The mechanics are not so important, but just know that the status of the port has to be given special attention in order to avoid memory errors.</li>
<li><code>activate()</code>: This is the function that runs when you run the experiment from the Bassoon GUI. It launches the psychopy windows that the stimuli and information are presented on, sets up some basic parameters, and then loops through each stimulus that has been added to the experiment bag, executing them in order. It also has some safety features for TTL handling, and calls the <code>reportTime()</code> function after each protocol if the user has requested it.</li>
</ul>
</li>
</ul></li>
</ul>
</p>
<p id = "programmaticAnchor">Experiments and protocols are created and manipulated through the Bassoon GUI. This is to make it as easy as possible to create, customize, and execute experiments. However, you can also access them programmatically, outside of the GUI: <br>
<pre style="background-color:rgb(243, 231, 215); margin-left:20%; margin-right:20%"><span class = "codeHighlight">from</span> psychopy <span class="codeHighlight">import</span> core, visual, data, event, monitors<br/><span class="codeHighlight">from</span> experiments.experiment <span class="codeHighlight">import</span> experiment<br/><span class="codeHighlight">from</span> protocols.protocol <span class="codeHighlight">import</span> protocol<br/><br/><span class="codeComment"># You must import each protocol that you want to use. Here I import just the flash stimulus as an example.</span><br/><span class = "codeHighlight">from</span> protocols.Flash <span class="codeHighlight">import</span> Flash<br/><br/><span class="codeComment"># Load an experiment</span><br>exp = experiment()<br/><br/><span class="codeComment"># Load the flash stimulus</span><br/>stim = Flash()<br/><br/><span class="codeComment"># Edit an attribute of the flash</span><br/>stim.stimTime=5 <span class="codeComment">#in seconds</span><br/><br/><span class="codeComment"># Add the stimulus to the experiment (put it in the "bag")</span><br/>exp.addProtocol(stim)<br/><br/><span class="codeComment"># Change an attribute of the experiment</span><br/>exp.useInformationMonitor=True<br/><br/><span class="codeComment"># Run the experiment</span><br/>exp.activate()
</pre>
</p>
<h3 id="mainpyAnchor">main.py</h3>
<p>The main.py file is the outter-most layer of Bassoon. It is used to launch the GUI, contains all of the code for every button and action, and can be thought of as the orchestrator of all Bassoon functionality. Tkinter is used for the graphical interface. The best way to understand how the GUI works is to look through the code and experiment with it. There are plenty of example snippets that can be used as a reference to implement your own buttons and functionality.</p>
<p>For most users, it will not be necessary to make any changes to the GUI. The only section of the main.py file that requires regular attention is the import statements at the top of the script. If you create a new stimulus (which can easily be done by making a new .py file in the <em>Bassoon/src/protocols</em> folder, copying the structure of a previously existing protocol file, and making adjustments for your new stimulus as needed), you will have to add a corresponding import statement to the top of main.py. This takes the form of <code>from protocols.myprotocol import myprotocol</code> (replacing <em>myprotocol</em> with whatever you named your new protocol).</p>
<h2 id="useAnchor">Use and Contributions</h2>
<p>Bassoon is intended for noncommercial use only. If you would like to use this software for commercial research, please <a href="mailto:mrscottharris@aol.com">send us a message</a> to inquire about a license.</p>
<p>If you use Bassoon for published science, please include a <a href="https://zenodo.org/doi/10.5281/zenodo.6757604" target="_blank">citation</a>.</p>
<p>Contributions to the <a href="https://github.com/ScottHarris17/Bassoon" target="_blank">Github repository</a> are welcomed. Suggestions or questions can also be directed to <a href="mailto:mrscottharris@aol.com">Scott Harris</a>.</p>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</body>
</html>