Skip to content

Instantly share code, notes, and snippets.

@qbilius
Forked from mpjdem/pyws.ipynb
Last active December 25, 2015 20:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save qbilius/7036997 to your computer and use it in GitHub Desktop.
Save qbilius/7036997 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Python for Vision Researchers"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook is meant as a one-day crash course in Python and PsychoPy. In one day you will learn the basics of Python just enough to build a real working experiment.\n",
"\n",
"There are more extensive resources for vision scientists on [our GestaltReVision wiki](http://gestaltrevision.be/wiki/python/python).\n",
"\n",
"**Authors:** Maarten Demeyer, [Jonas Kubilius](http://klab.lt) \n",
"**Year:** 2013 \n",
"**Copyright:** Public Domain, duh "
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"What you'll need"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Unfortunately, getting Python and all necessary packages is one of the major difficulties for beginners. So please be patient and try to get everything in order because once you get it running, magic can start."
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Python packages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For complete beginners, a [Standalone PsychoPy installation](http://sourceforge.net/projects/psychpy/files/PsychoPy/) will be the easiest. However, if you want to be able to run this notebook on your machine, you'll be better off installing all packages manually. See [our website](http://gestaltrevision.be/wiki/python/pythoninstall) or [PsychoPy's installation page](http://www.psychopy.org/installation.html) for more information.\n",
"\n",
"We also suggest to install [Spyder](https://code.google.com/p/spyderlib/) as a decent environment to code and run your experiments, or you can try [other options](http://gestaltrevision.be/wiki/python/pythoninstall)."
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Tutorial code"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All materials, including this notebook, are available [here](http://gestaltrevision.be/wiki/_media/python/python_for_visres.zip)."
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"*Code advancement 0*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The goal of this course is to build a *Change blindness* experiment. We will work with pairs of images that have one tiny detail different amond them, for example, a green traffic light might turn into red in a typical street scene. We will present these pairs continuously flipping until a participant observes a difference between the two. To make this change less prominent, we will also have bubbles appearing at random sizes and locations upon each image flip. If the participant detects a change, she should hti a spacebar. Otherwise, after 30 seconds the experiment advances to the next stimulus (initiated by a spacebar).\n",
"\n",
"If this is not very clear, download the code of this experiment and try running it yourself first.\n",
"\n",
"**Question:** How would you go about creating this experiment? We understand you don't know how to program yet but simply try to list all steps that such experiment should perform. The answer is below but you will benefit massively from trying to produce it yourself."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"#====================================================================\n",
"# Settings that we might want to tweak later on\n",
"#====================================================================\n",
"\n",
"# Directory to save data in (data)\n",
"# Directory where imagesc an be found (image)\n",
"# Image names (without the suffixes, like 1, 2, ...)\n",
"# Suffix for the first image (a.jpg)\n",
"# Suffix for the second image (b.jpg)\n",
"# Screen size in pixels (1024x768)\n",
"# Image freezing time (30s)\n",
"# Image changing time (0.5s)\n",
"# Number of bubbles overlayed on the image (40)\n",
"\n",
"\n",
"#==========================================\n",
"# Store info about the experiment session\n",
"#==========================================\n",
"\n",
"# Get subject name, gender, age, handedness\n",
"# Get date and time\n",
"# Store this information for output\n",
"# Create a unique filename for the experiment data\n",
"\n",
"\n",
"#=========================\n",
"# Prepare conditions lists\n",
"#=========================\n",
"\n",
"# Make images list, in random order\n",
"# - Find images and remember their names\n",
"# - Randomize order of images\n",
"\n",
"# Make orientations list\n",
"# - Half images upright, half inverted\n",
"# - Randomize order of orientations\n",
"\n",
"\n",
"#===============================\n",
"# Creation of window and stimuli\n",
"#===============================\n",
"\n",
"# Open window\n",
"# Create the start message\n",
"# Define stimuli (contents can still change)\n",
"# Define bubbles (positions and sizes can still change)\n",
"\n",
"\n",
"#=========================\n",
"# Creation of TrialHandler\n",
"#=========================\n",
"\n",
"# Define a list of trials with their properties:\n",
"# - Which image\n",
"# - Which orientation\n",
"\n",
"\n",
"#=====================\n",
"# Start the experiment\n",
"#=====================\n",
"\n",
"# Display instructions\n",
"\n",
"# Run through the trials. On each trial:\n",
"# - Wait for a spacebar to start a trial\n",
"# - Read both image files, set their orientation\n",
"# - Then switch the image every 0.5s, while:\n",
"# - Repositioning and resizing bubbles each time\n",
"# - Listening for a spacebar press\n",
"# - Stop if spacebar has been pressed, or if 30s have passed\n",
"# - Record response time\n",
"# - Advance to the next trial\n",
"\n",
"# When all trials are done, write the data to a file and quit"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Literals, expressions, and variables"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We will need our program to hold and handle many pieces of information. For instance, the reaction time recorded on a trial, or the duration of the image presentation. \n",
"\n",
"As a starting point, we distinguish between three useful types of values:\n",
"\n",
"- **Integer**: whole numbers, for instance '5'\n",
"- **Float**: decimal numbers, for instance '5.3'\n",
"- **Boolean**: binary numbers, coded as `True` or `False`\n",
"\n",
"These value representations are called **literals**, since you would literally put into your program which value you want to be there. \n",
"\n",
"More commonly however, values are implied in **expressions**, which evaluate (e-value-ate) to a value. For instance, '5+3' does not represent the number 8 literally, but if you work it out, it does evaluate to 8. Therefore, an expression is any piece of code which *represents a value*.\n",
"\n",
"Many values change during the execution of a program while they retain the same meaning; or, they might recur so often throughout the program that it is more convenient to represent them by a symbolic name instead. For this, we have **variables** which have a fixed *name*, and a *value* that is not fixed. To assign a value to a variable, use the following syntax:\n",
"\n",
"`name = value`\n",
"\n",
"To then see the current value of a variable, do:\n",
"\n",
"`print name`\n",
"\n",
"Since we are for now considering three types of values (integer, float, boolean), we also need three types of variables. Unlike other programming languages, Python does not require you to define the type of a variable yourself; Python will figure it out from the values you assign. To inspect the type of any variable once it has been assigned, do:\n",
"\n",
"`print type(name)`"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Assign values to the three variable types\n",
"# Here, we simply assign a literal\n",
"my_int = 5\n",
"print my_int\n",
"print type(my_int)\n",
"\n",
"my_float = 5.0\n",
"print my_float\n",
"print type(my_float)\n",
"\n",
"my_boolean = False\n",
"print my_boolean\n",
"print type(my_boolean)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"5\n",
"<type 'int'>\n",
"5.0\n",
"<type 'float'>\n",
"False\n",
"<type 'bool'>\n"
]
}
],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"first_number = 5\n",
"second_number = 5.0\n",
"\n",
"## Now we assign an expression to a variable\n",
"## Since an expression evaluates to a value, this is equivalent\n",
"\n",
"# Simple expression\n",
"summed_numbers = first_number + second_number\n",
"print summed_numbers, type(summed_numbers)\n",
"\n",
"# Re-assign a variable using an expression involving its own value\n",
"first_number = first_number * 3\n",
"print first_number\n",
"\n",
"# A more complex expression\n",
"result = ((first_number-3)*second_number)/1.3\n",
"print result"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"10.0 <type 'float'>\n",
"15\n",
"46.1538461538\n"
]
}
],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You might note that a literal is itself actually a special case of an expression, since a literal is also a piece of code which represents a value."
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Statements and program flow"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A program typically consists of a sequence of short lines, each of which *does something*. One such line of code which *does something* is called a **statement**. \n",
"\n",
"Above, we have used **assignment statements** to assign values to variables, and **print statements** to print variable values to the screen. \n",
"\n",
"But, if we want our program to be more than a glorified calculator, we need automated *program flow*. Two kinds of statements are of fundamental importance then: conditional statements and iterative statements.\n",
"\n",
"The basic **conditional statement** is the *if statement*, which only executes the next *block* of statements if it is followed by an expression which evaluates to `True`. For instance:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = 1\n",
"b = 2\n",
"\n",
"if True:\n",
" print a \n",
"\n",
"if False:\n",
" print b"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1\n"
]
}
],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note the colon : at the end of the if-statement, which must be present. Note also that the block of code conditional on the if-statement must be *indented*, preferably by four spaces.\n",
"\n",
"Of course, this code is pretty useless, since we know the conditional value will always be True. We could also replace the condition by a variable which is equal to, and therefore evaluates to a boolean:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = 1\n",
"my_boolean = True\n",
"\n",
"if my_boolean:\n",
" print a "
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1\n"
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This can sometimes be useful. However more commonly, we will not represent the `True` or `False` value of the if-condition by a literal or a variable, but by a more complex expression. For instance, `a > 0` would evaluate to `True`. The resulting value of the expression is only used in this line of code, so there is no actual need to store it in a variable first, we can just insert the expression itself. "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = 1\n",
"\n",
"# This expression evaluates to a boolean\n",
"my_boolean = a > 0\n",
"print my_boolean\n",
"\n",
"# We can assign to a variable and use that\n",
"if my_boolean:\n",
" print a\n",
"\n",
"# Or we could use the full expression directly\n",
"if a > 0:\n",
" print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"True\n",
"1\n",
"1\n"
]
}
],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can specify alternate options if the condition is not satisfied, and evaluate integers or floats to booleans in various operations."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = 1\n",
"b = 2\n",
"c = 3\n",
"\n",
"if a > 0:\n",
" print a\n",
" \n",
"elif b == 2: \n",
" # Execute if b equals 2\n",
" # ...but ONLY if the first condition was False\n",
" # Note the difference with the assignment symbol =\n",
" print b \n",
" \n",
"elif c <= 3: \n",
" # Execute if c is smaller than or equal to 3\n",
" # ...but ONLY if the first two conditions were False\n",
" print c \n",
" \n",
"else: \n",
" # Execute in any case\n",
" # But ONLY if all others were False\n",
" print 4 \n",
"\n",
" \n",
"if b >= 0: \n",
" # Execute if b is greater than or equal to 0\n",
" # A new if-statement, evaluated independent of previous conditions\n",
" print b \n",
" \n",
" \n",
"# This statement is not indented\n",
"# ...and will therefore always execute\n",
"print 5"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1\n",
"2\n",
"5\n"
]
}
],
"prompt_number": 6
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = 1\n",
"b = 2\n",
"c = 3\n",
"\n",
"# We can logically combine booleans to create another boolean\n",
"# using 'and', 'or' and 'not'\n",
"my_boolean = a > 0 and b == 2\n",
"print my_boolean\n",
"\n",
"# Instead of assigning to a variable, again we can use\n",
"# the full expression in the conditional statement\n",
"if a > 0 and b == 2:\n",
" print a, b \n",
"elif b == 3 or c <= 3:\n",
" print b, c\n",
"elif not a < 0 and c == 3:\n",
" print a, c"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"True\n",
"1 2\n"
]
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we have really started programming! Next, we need an **iterative statement** which can re-execute statements without us having to keep entering them literally in the program. \n",
"\n",
"The most basic type is the *while statement*, which repeatedly executes its code block as long as its conditional expression evaluates to `True`. "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Instead of this:\n",
"a = 1\n",
"print a\n",
"a = a + 1\n",
"print a\n",
"a = a + 1\n",
"print a\n",
"a = a + 1\n",
"print a\n",
"a = a + 1\n",
"print a\n",
"\n",
"# (copy-paste as many times as you need...)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1\n",
"2\n",
"3\n",
"4\n",
"5\n"
]
}
],
"prompt_number": 8
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# We can write much shorter and far more flexibly:\n",
"a = 1\n",
"maximum_number = 5\n",
"\n",
"while a <= maximum_number:\n",
" print a\n",
" a = a + 1"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1\n",
"2\n",
"3\n",
"4\n",
"5\n"
]
}
],
"prompt_number": 9
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To stop your while-loop at any time, use the **break** statement."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = 1\n",
"maximum_number = 5\n",
"\n",
"while True:\n",
" print a\n",
" a = a + 1\n",
" if a > maximum_number:\n",
" break"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1\n",
"2\n",
"3\n",
"4\n",
"5\n"
]
}
],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With what you have just learned, **you could in principle write any program imaginable!**\n",
"\n",
"However, they would often be long, repetitive and difficult to understand. All the concepts we will next learn are therefore meant to *make the solution more simple when the problem you are trying to solve is more complex.*"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"More data types"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now introduce more complex variable types. The first three (**string**, **tuple** and **list**) are ordered series of values, whereas the fourth (**dictionary**) connects pairs of values to one another, in no particular order. \n",
"\n",
"All four have a way to display their length, **len()** "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# A string represents an ordered series of characters\n",
"my_string = \"a line of text\"\n",
"my_string = 'a line of text'\n",
"\n",
"print my_string\n",
"print type(my_string)\n",
"print len(my_string)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"a line of text\n",
"<type 'str'>\n",
"14\n"
]
}
],
"prompt_number": 11
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# A tuple is an ordered series of values\n",
"# ... but these values may consist of different data types\n",
"\n",
"my_tuple = (1, 2.0, False)\n",
"\n",
"print my_tuple\n",
"print type(my_tuple)\n",
"print len(my_tuple)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"(1, 2.0, False)\n",
"<type 'tuple'>\n",
"3\n"
]
}
],
"prompt_number": 12
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# A list is similar (see below for the difference)\n",
"# Note that tuples and lists may even have complex data types as elements\n",
"\n",
"my_list = [True, \"a string!\", (1,'tuple','in', 1, 'list')] \n",
"\n",
"print my_list\n",
"print type(my_list)\n",
"print len(my_list)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[True, 'a string!', (1, 'tuple', 'in', 1, 'list')]\n",
"<type 'list'>\n",
"3\n"
]
}
],
"prompt_number": 13
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# A dictionary connects values to one another\n",
"# It is unordered; the order of elements does not matter\n",
"# Again, all variable types may be used\n",
"\n",
"my_dictionary = {1:[1,1,1], 3:'three', False:2.0}\n",
"print my_dictionary\n",
"\n",
"my_dictionary = {False:2.0, 1:[1,1,1], 3:'three'}\n",
"print my_dictionary\n",
"\n",
"print type(my_dictionary)\n",
"print len(my_dictionary)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"{False: 2.0, 1: [1, 1, 1], 3: 'three'}\n",
"{False: 2.0, 1: [1, 1, 1], 3: 'three'}\n",
"<type 'dict'>\n",
"3\n"
]
}
],
"prompt_number": 14
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To retrieve a particular element, use square brackets. In case of an ordered series, this will retrieve the n-th element, where the **index** count starts at 0. \n",
"\n",
"Note that *strings and tuples cannot be modified* once assigned, unless re-assigned, whereas this is possible for lists and dictionaries."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = \"kumbaya, milord\"\n",
"print a\n",
"\n",
"# Fetch and print the first element of a string\n",
"print a[0]\n",
"\n",
"# Fetch the first until, but not including, the fifth element\n",
"# This is called 'slicing'\n",
"print a[0:4]\n",
"\n",
"# -1 is the index of the last element\n",
"# -2 of the second-to-last, etc\n",
"print a[-2]\n",
"print a[-5:-1]\n",
"print a[-5:]\n",
"\n",
"# Reverse the order or change step size\n",
"# Syntax: [start:end:step]\n",
"print a[-1:-10:-1]\n",
"print a[0::3]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"kumbaya, milord\n",
"k\n",
"kumb\n",
"r\n",
"ilor\n",
"ilord\n",
"drolim ,a\n",
"kbamo\n"
]
}
],
"prompt_number": 15
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# The same applies to the other ordered data types\n",
"b = [1, 2.0, True]\n",
"print b\n",
"\n",
"# In lists, you can change existing elements\n",
"# Change the second element in a list\n",
"b[1] = 'foo'\n",
"print b\n",
"\n",
"# Print the elements in reverse\n",
"print b[::-1]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[1, 2.0, True]\n",
"[1, 'foo', True]\n",
"[True, 'foo', 1]\n"
]
}
],
"prompt_number": 16
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# '+' performs concatenation on strings, tuples and lists\n",
"# Since we do not re-assign the result to a variable however\n",
"# ...nothing changes in the original ordered series\n",
"\n",
"a = \"kumbaya, milord\"\n",
"print a\n",
"print a + '!!!'\n",
"print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"kumbaya, milord\n",
"kumbaya, milord!!!\n",
"kumbaya, milord\n"
]
}
],
"prompt_number": 17
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Same goes for a tuple (or a list)\n",
"b = (1, 2.0, True)\n",
"print b\n",
"print b + (5,)\n",
"print b\n",
"\n",
"# By re-assigning, we do change the tuple\n",
"b = b + (5,)\n",
"print b"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"(1, 2.0, True)\n",
"(1, 2.0, True, 5)\n",
"(1, 2.0, True)\n",
"(1, 2.0, True, 5)\n"
]
}
],
"prompt_number": 18
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In case of a **dictionary**, square brackets will retrieve the second value of each pair through the first value. "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Retrieve the value corresponding to 1 in the dictionary\n",
"# Note that this retrieval is one-way only\n",
"# ...therefore the first values (the 'keys') need to be unique\n",
"\n",
"d = {1:'one', 2:'two', 3:'three'}\n",
"print d[1]\n",
"\n",
"# Add a value to the dictionary\n",
"d[4] = 'four'\n",
"print d"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"one\n",
"{1: 'one', 2: 'two', 3: 'three', 4: 'four'}\n"
]
}
],
"prompt_number": 19
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We won't discuss all possible options when working with strings, tuples, lists and dictionaries.\n",
"\n",
"A useful one however is the **in** keyword, which evaluates to a boolean whether a given element is present or not."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# String\n",
"a = 'example'\n",
"result = 'i' in a\n",
"print result\n",
"\n",
"# Tuple\n",
"b = (1,2,3)\n",
"print 0 in b\n",
"\n",
"# List\n",
"c = [1,2,3]\n",
"print 5 in c\n",
"\n",
"# In case of a dictionary, this pertains only to the keys\n",
"d = {1:'one', 2:'two', 3:'three'}\n",
"print 2 in d\n",
"print 'two' in d"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"False\n",
"False\n",
"False\n",
"True\n",
"False\n"
]
}
],
"prompt_number": 20
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"More program flow"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The next basic ingredient we will add here is a new iterative statement, the **for-statement**. 'for' allows us to run through each element of an ordered series, and execute statements on them. "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Without the for-syntax, we could do it like this\n",
"a = 'hi!'\n",
"n = 0\n",
"while n < len(a):\n",
" print a[n]\n",
" n = n + 1"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"h\n",
"i\n",
"!\n"
]
}
],
"prompt_number": 21
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# With the for-syntax, this becomes much more intuitive \n",
"a = 'hi!'\n",
"for letter in a:\n",
" print letter"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"h\n",
"i\n",
"!\n"
]
}
],
"prompt_number": 22
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A final, brutal instrument in program flow control is the **exception**. Also known as: an error. Errors are not just annoying, as a programmer they are actually useful, since they allow you to exit the program at any point by *raising* an exception, and telling the user what went wrong."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = -1\n",
"\n",
"if a < 0:\n",
" raise Exception('Your dog cannot have a negative number of paws!')\n",
"\n",
"print 'My dog has', a, 'paw(s).'"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "Exception",
"evalue": "Your dog cannot have a negative number of paws!",
"output_type": "pyerr",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mException\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-23-bea06fc204be>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0ma\u001b[0m \u001b[1;33m<\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'Your dog cannot have a negative number of paws!'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;32mprint\u001b[0m \u001b[1;34m'My dog has'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'paw(s).'\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mException\u001b[0m: Your dog cannot have a negative number of paws!"
]
}
],
"prompt_number": 23
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Debugging"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Unfortunately you do not always need to raise errors yourselves. You will make mistakes (called 'bugs') and errors will occur. This is normal.\n",
"\n",
"An important skill for any programmer is therefore to **debug** his program efficiently. Sometimes, the error message itself is informative enough."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"aseries = (0,2,3)\n",
"a_series[0] = 1"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'a_series' is not defined",
"output_type": "pyerr",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-24-e8ce4980ae2f>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0maseries\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0ma_series\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mNameError\u001b[0m: name 'a_series' is not defined"
]
}
],
"prompt_number": 24
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"More often however the cause of the error is not as readily apparent, or there might be no error at all, just unexpected behavior. For instance, debug this:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"my_string = 'Hi!'\n",
"my_list = []\n",
"\n",
"# We try to turn a string into a list\n",
"# of individual characters\n",
"n = 0\n",
"while True:\n",
" my_list + [my_string[n]]\n",
" n = n + 1\n",
" if n > len(my_string):\n",
" break\n",
"print my_list"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "IndexError",
"evalue": "string index out of range",
"output_type": "pyerr",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mIndexError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-25-37fc06eadd9a>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[0mn\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32mwhile\u001b[0m \u001b[0mTrue\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 8\u001b[1;33m \u001b[0mmy_list\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mmy_string\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mn\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 9\u001b[0m \u001b[0mn\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mn\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 10\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[1;33m>\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmy_string\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mIndexError\u001b[0m: string index out of range"
]
}
],
"prompt_number": 25
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The most basic form of debugging, is to add print statements at every iteration, so you can see what is happening. Do this now.\n",
"\n",
"More advanced options:\n",
"\n",
"* In Spyder, inspect variable values in the **Variable Explorer** tab.\n",
"* You can also use the built-in **pdb** module that helps with debugging. There is a nice introducion to the topic on [our wiki](http://gestaltrevision.be/wiki/python/debugging). In Spyder, debugging is done by double-clicking on the relevant line. A red dot will appear and you can then run your script in the debug mode."
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Intermediate summary"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Concepts**\n",
"\n",
"- literals\n",
"- expressions\n",
"- variables\n",
"- statements\n",
"\n",
"**Data types**\n",
"\n",
"- int\n",
"- float\n",
"- bool\n",
"- str (ordered, immutable, only characters)\n",
"- tuple (ordered, immutable, all types)\n",
"- list (ordered, mutable, all types)\n",
"- dictionary (unordered, mutable, all types, unique keys)\n",
"\n",
"**Statements**\n",
"\n",
"- =\n",
"- print\n",
"- if elif else\n",
"- while\n",
"- break\n",
"- for\n",
"- raise\n",
"\n",
"**Also useful**\n",
"\n",
"- #\n",
"- type()\n",
"- len()\n",
"- `+ - * / ** %`\n",
"- `< > <= >= ==`\n",
"- and or not\n",
"- [ ]\n",
"- in\n"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"*Code advancement: 1*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Using what we learned so far, try to subsitute as much of pseudocode from *Code advancement 0* with the real code. The answer is below but please put an effort into working it out by yourself."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"#====================================================================\n",
"# Settings that we might want to tweak later on\n",
"#====================================================================\n",
"\n",
"datapath = 'data' # directory to save data in\n",
"impath = 'images' # directory where images can be found\n",
"imlist = ['1','2','3','4','5','6'] # image names (without the suffixes)\n",
"asfx = 'a.jpg' # suffix for the first image\n",
"bsfx = 'b.jpg' # suffix for the second image\n",
"scrsize = (1024,768) # screen size (pixels)\n",
"timelimit = 30 # image freezes after this time (seconds)\n",
"changetime = .5 # how often images are changing (seconds)\n",
"n_bubbles = 40 # number of bubbles overlayed on the image\n",
"\n",
"\n",
"#==========================================\n",
"# Store info about the experiment session\n",
"#==========================================\n",
"\n",
"# Show a dialog box to enter session information\n",
"exp_name = 'Change Detection'\n",
"exp_info = {}\n",
"\n",
"# Get subject name\n",
"# Get date and time\n",
"# Put both in the exp_info dictionary\n",
"\n",
"data_fname = exp_info['participant'] + '_' + exp_info['date']\n",
"\n",
"\n",
"#=========================\n",
"# Prepare conditions lists\n",
"#=========================\n",
"\n",
"# Make images list, in random order\n",
"# - Find images and remember their names\n",
"# - Randomize order of images\n",
"\n",
"# Make orientations list\n",
"# - Half images upright, half inverted\n",
"# - Randomize order of orientations\n",
"\n",
"\n",
"#===============================\n",
"# Creation of window and stimuli\n",
"#===============================\n",
"\n",
"# Open window\n",
"\n",
"# Create the start message\n",
"start_text = 'Press space to start the experiment'\n",
"\n",
"# Define stimuli (contents can still change)\n",
"# Define bubbles (positions and sizes can still change)\n",
"\n",
"\n",
"#=========================\n",
"# Creation of TrialHandler\n",
"#=========================\n",
"\n",
"# Define a list of trials with their properties:\n",
"# - Which image\n",
"# - Which orientation\n",
"\n",
"\n",
"#=====================\n",
"# Start the experiment\n",
"#=====================\n",
"\n",
"# Display instructions\n",
"\n",
"# Run through the trials\n",
"for trial in trials:\n",
"# - Wait for a spacebar to start a trial\n",
"# - Read both image files\n",
" while no_response and time < timelimit:\n",
"# - Switch the image every 0.5s\n",
"# - Reposition and resize the bubbles\n",
"# - Stop if spacebar has been pressed, or if 30s have passed\n",
"# - Record response time\n",
"\n",
"# When all trials are done, write the data to a file and quit"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We have learned how to represent values or even series of values by a short variable name, so that we can re-use it easily.\n",
"\n",
"Often however we wish to quickly repeat many lines of code. For instance, the formula for a sine is complicated, we would not want to copy-paste it into the code for every angle for which we want to compute a sine. We would rather refer to the relevant code by a short name, such as `sin`.\n",
"\n",
"**Functions** allow this. To define a function, use a **def** statement:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def print_something():\n",
" print \"preparing to print\"\n",
" print \"the function is printing\"\n",
" print \"done printing\"\n",
"\n",
"print_something()\n",
"print_something()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"preparing to print\n",
"the function is printing\n",
"done printing\n",
"preparing to print\n",
"the function is printing\n",
"done printing\n"
]
}
],
"prompt_number": 26
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To add more flexibility, we can pass variables to the function, called the function **arguments**, as well as **return** variables back to the main block of code."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Function definition\n",
"def print_and_sum(a, b):\n",
" print 'The first number is', a\n",
" print 'The second number is', b\n",
" return a + b\n",
"\n",
"# Assign function output to a variable\n",
"c = print_and_sum(3,5)\n",
"print 'Their sum is', c\n",
"\n",
"# Print function output directly\n",
"print print_and_sum(10,20)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"The first number is 3\n",
"The second number is 5\n",
"Their sum is 8\n",
"The first number is 10\n",
"The second number is 20\n",
"30\n"
]
}
],
"prompt_number": 27
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that our **function call** is by itself an expression, since it returns and therefore evaluates to a value. Multiple return values, separated by a comma, are returned as a tuple. Functions without a return argument (e.g. the first example) return `None`.\n",
"\n",
"Defining functions is often important when you write long, repetitive programs. However even more important is that others have defined common functions for you, which you then do not need to re-invent, or even understand how they work! \n",
"\n",
"Actually, when you used `len()` or `type()` above, you were doing exactly this - you were using **built-in functions**. We could also write our own `len()` function, but why bother, when it exists already?"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def my_len(inp):\n",
" x = 0\n",
" for el in inp:\n",
" x = x + 1\n",
" return x\n",
"\n",
"a = [1, True, 'T']\n",
"\n",
"print len(a)\n",
"print my_len(a)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"3\n",
"3\n"
]
}
],
"prompt_number": 28
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Modules"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Suppose we have a collection of functions that, loosely or not, belong together. For instance, a bunch of trigonometric functions.\n",
"\n",
"We can then put this code in one .py file, whereas we can put unrelated code in a separate .py file. Such a .py file is then called a **module**, which we can load again in other Python programs, and use its functions.\n",
"\n",
"For instance, try to save this code to a file called `multiplications.py`:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def times_two(x):\n",
" print x*2\n",
" \n",
"def times_three(x):\n",
" print x*3"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 31
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, if put in the same directory as your current Python script, we can **import** the module using simply the filename. Its functions are accessed using the **dot operator .**"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import multiplications\n",
"\n",
"a = 5\n",
"multiplications.times_two(a)\n",
"multiplications.times_three(a)"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To address a module in a shorter way, we can specify our own name for the module, or even just import some of the functions directly, in which case we don't even need to use the module name anymore."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import multiplications as mt\n",
"\n",
"mt.times_two(5)"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from multiplications import times_two\n",
"\n",
"times_two(5)"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In larger software libraries, such as PsychoPy, you can encounter modules-within-modules, to organize the wealth of code in a logical, hierarchical way. The overarching module-containing-modules is then called a **package**."
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Objects"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In many situations, certain variables and functions are very closely connected to one another. Take for instance a list variable, and the function `append_element()` which appends a new element to it."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = [1,2,3]\n",
"print a\n",
"\n",
"def append_element(old_list, new_element):\n",
" return old_list + [new_element]\n",
"\n",
"a = append_element(a,4)\n",
"print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[1, 2, 3]\n",
"[1, 2, 3, 4]\n"
]
}
],
"prompt_number": 33
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The function needs a list, and the list has great use for the function. They belong together. This is why we can group variables and functions into an **object**.\n",
"\n",
"It is not within the scope of this tutorial to define our own objects. However, since they are encountered everywhere in Python, you do need to understand how to use those that were made for you by others. Often you will not address the **member variables** of an object directly after their assignment, but you will use **member functions** that use and/or affect these member variables.\n",
"\n",
"Actually, you have already been using objects. All variables in Python are objects, even a simple integer. This becomes more apparent with the more complex data types, such as a list, that have convenient member functions. Through the **. (dot) operator**, you can use these member functions. \n",
"\n",
"As you will see, many of the common operations which you would want to perform on a list have already been implemented for you... even the append function!"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Do the same thing \n",
"# ...using the append member function of the list object\n",
"a = [1,2,3]\n",
"a.append(4)\n",
"print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[1, 2, 3, 4]\n"
]
}
],
"prompt_number": 34
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# reverse() reverses the list\n",
"a.reverse()\n",
"print a\n",
"\n",
"# remove() removes its first encounter of the specified element\n",
"a.remove(2)\n",
"print a\n",
"\n",
"# sort() sorts the list from low to high\n",
"a.sort()\n",
"print a\n",
"\n",
"# pop() returns AND removes the last element\n",
"print a.pop()\n",
"print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[4, 3, 2, 1]\n",
"[4, 3, 1]\n",
"[1, 3, 4]\n",
"4\n",
"[1, 3]\n"
]
}
],
"prompt_number": 35
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the above cases, the actual contents of the list are stored in some member variable of the list object, which represents its current value. The member functions then use this member variable to perform their operations on, and replace it by their output, so that the contents of the list change by executing the member function. \n",
"\n",
"We call this **in-place** functions; they modify the object's contents, and typically do not return anything (i.e., `None`)."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print a\n",
"print a.reverse()\n",
"print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[1, 3]\n",
"None\n",
"[3, 1]\n"
]
}
],
"prompt_number": 36
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Other objects, like a string, are however immutable, as you may remember; you cannot change their contents. The in-place list functions will not work on string.\n",
"\n",
"Instead, strings often have member functions which take the contents of the string object, process it, and **return a new object**. If the returned object is a modified version of the string, you may then choose to assign that string to the same variable name, in which case the old string will be replaced by the new one, to the same result as an in-place function."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = 'kumbaya, milord'\n",
"\n",
"# Split at spaces, and return a list of strings\n",
"print a.split(' ')\n",
"\n",
"# To print the second word in a sentence, you could then do\n",
"print a.split(' ')[1]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"['kumbaya,', 'milord']\n",
"milord\n"
]
}
],
"prompt_number": 37
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Check whether a string starts or ends with certain characters\n",
"# Returns a boolean\n",
"print a.startswith('kumba')\n",
"print a.endswith('ard')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"True\n",
"False\n"
]
}
],
"prompt_number": 38
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Remove the first/last part of a string\n",
"# ...note that the original string is not changed\n",
"# A new, different string is returned instead\n",
"print a.lstrip('kumba')\n",
"print a.rstrip('ord')\n",
"print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"ya, milord\n",
"kumbaya, mil\n",
"kumbaya, milord\n"
]
}
],
"prompt_number": 39
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Replace part of a string\n",
"# Again, a new string is returned\n",
"print a.replace('lord', 'lard')\n",
"\n",
"# Here we assign the result to the original string variable\n",
"# To the same effect as an in-place function\n",
"a = a.replace('u','x')\n",
"print a"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"kumbaya, milard\n",
"kxmbaya, milord\n"
]
}
],
"prompt_number": 40
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Example: The PsychoPy TrialHandler"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`psychopy` defines a new, useful *type* of objects (much like `float` is the *type* of '5.0') called `TrialHandler`. As we will see, it does not allow you to do anything you couldn't do without using objects, but it does make your life simpler. \n",
"\n",
"Remember our experiment - defining our trial are two pieces of information: the image to be presented, and the orientation of the image. After the trial, we want to obtain the accuracy and the reaction time. We could have handled this as follows:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"im_order = [5, 6, 3, 2, 1, 4]\n",
"ori_order = [0, 1, 1, 0, 1, 0]\n",
"trials_n = [1, 2, 3, 4, 5, 6]\n",
"data_acc = []\n",
"data_rt = []\n",
"\n",
"for trn in trials_n:\n",
" current_image = im_order[trn-1]\n",
" current_orientation = ori_order[trn-1]\n",
" \n",
" # Present the correct stimuli\n",
" # ...then gather the responses as acc and rt\n",
" # e.g.,\n",
" acc = 1\n",
" rt = 0.32\n",
" \n",
" data_acc.append(acc)\n",
" data_rt.append(rt)\n",
"\n",
"print data_acc\n",
"print data_rt\n",
"\n",
"# Find some function that turns all these lists into a text file\n",
"# Preferably easily loadable by your statistics program"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[1, 1, 1, 1, 1, 1]\n",
"[0.32, 0.32, 0.32, 0.32, 0.32, 0.32]\n"
]
}
],
"prompt_number": 41
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In practice, these lists belong together, and you'll always want to perform the same actions on them. Define your conditions, loop over your trials, add responses to each trial, and save everything to a file. In addition you might want to add error checking, for instance to make sure that all lists are of equal length. \n",
"\n",
"psychopy bundles all this and more into a convenient object for you, so that you don't need to repeatedly code the same things over and over.\n",
"\n",
"--\n",
"\n",
"The `TrialHandler` is set up using a **constructor**, which much like a function takes certain input arguments. Here, the condition values you want to use, and some settings. \n",
"\n",
"This information is then stored inside the TrialHandler object, but we won't care exactly how or where. We just use the object's functions.\n",
"\n",
"Then, **looping** over the `TrialHandler` returns one trial at a time, containing the values for that specific trial in the form of a dictionary.\n",
"\n",
"Adding responses is done through the **`addData()`** member function. Since `TrialHandler` remembers what the current trial is, you don't need to specify that. \n",
"\n",
"Finally, the **`SaveAsWideText()`** function saves both the condition values and the response values to a text file for you."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# import the TrialHandler\n",
"from psychopy.data import TrialHandler\n",
"\n",
"# Define conditions values as a list of dicts\n",
"stim_order = [{'im':5,'ori':0},\n",
" {'im':6,'ori':1},\n",
" {'im':3,'ori':1},\n",
" {'im':2,'ori':0},\n",
" {'im':1,'ori':1},\n",
" {'im':4,'ori':0}]\n",
"\n",
"# Construct the TrialHandler object\n",
"# nReps = number of repeats\n",
"# method = randomization method (here: no randomization)\n",
"trials = TrialHandler(stim_order, nReps=1, method='sequential')\n",
"\n",
"# Loop over the trials\n",
"for trial in trials:\n",
" \n",
" print trial['im'], trial['ori']\n",
" \n",
" # Present the correct stimuli\n",
" # ...then gather the responses as acc and rt\n",
" # e.g.,\n",
" acc = 1\n",
" rt = 0.32\n",
" \n",
" trials.addData('acc',acc)\n",
" trials.addData('rt',rt)\n",
"\n",
"# And save everything to a file\n",
"trials.saveAsWideText('test.csv', delim=',')"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The main practical **difference between a module and an object** (for now) is that you can easily create several instances of a given object type (many different strings, several TrialHandlers, ...), whereas you should import a module only once in a script. Module functions typically act on data you provide in the individual function arguments, whereas object functions act on variables that are present within the object."
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Intermediate summary"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Functions**\n",
"\n",
"- Function definition (def)\n",
"- Input arguments\n",
"- Output arguments\n",
"- Function call\n",
"\n",
"**Modules**\n",
"\n",
"- Filename = module name\n",
"- import ...\n",
"- import ... as ...\n",
"- from ... import ...\n",
"- . (dot) operator\n",
"\n",
"**Objects**\n",
"\n",
"- Member variables (can be hidden)\n",
"- Member functions\n",
"- . (dot) operator\n",
"- In-place functions"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"*Code advancement 2*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You have now learned a couple more useful functions which should help you further advance with the code from *Code advancement 2*. The answer is below."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"#===============\n",
"# Import modules\n",
"#===============\n",
"\n",
"from psychopy import data\n",
"\n",
"#====================================================================\n",
"# Settings that we might want to tweak later on\n",
"#====================================================================\n",
"\n",
"datapath = 'data' # directory to save data in\n",
"impath = 'images' # directory where images can be found\n",
"imlist = ['1','2','3','4','5','6'] # image names (without the suffixes)\n",
"asfx = 'a.jpg' # suffix for the first image\n",
"bsfx = 'b.jpg' # suffix for the second image\n",
"scrsize = (1024,768) # screen size (pixels)\n",
"timelimit = 30 # image freezes after this time (seconds)\n",
"changetime = .5 # how often images are changing (seconds)\n",
"n_bubbles = 40 # number of bubbles overlayed on the image\n",
"\n",
"\n",
"#==========================================\n",
"# Store info about the experiment session\n",
"#==========================================\n",
"\n",
"# Show a dialog box to enter session information\n",
"exp_name = 'Change Detection'\n",
"exp_info = {}\n",
"\n",
"# Get subject name\n",
"# Get date and time\n",
"# Put both in the exp_info dictionary\n",
"\n",
"data_fname = exp_info['participant'] + '_' + exp_info['date']\n",
"\n",
"\n",
"#=========================\n",
"# Prepare conditions lists\n",
"#=========================\n",
"\n",
"# Make images list, in random order\n",
"# - Find images and remember their names\n",
"# - Randomize order of images\n",
"\n",
"# Make orientations list\n",
"# - Half images upright, half inverted\n",
"# - Randomize order of orientations\n",
"\n",
"\n",
"#===============================\n",
"# Creation of window and stimuli\n",
"#===============================\n",
"\n",
"# Open window\n",
"\n",
"# Create the start message\n",
"start_text = 'Press space to start the experiment'\n",
"\n",
"# Define stimuli (contents can still change)\n",
"# Define bubbles (positions and sizes can still change)\n",
"\n",
"\n",
"#=========================\n",
"# Creation of TrialHandler\n",
"#=========================\n",
"\n",
"# Create a list of dictionaries\n",
"stim_order = [{},{},{},{},{},{}]\n",
"# Fill this in using im_order and ori_order\n",
"# We can do this already, but we'll learn a shorter way\n",
"\n",
"trials = data.TrialHandler(stim_order, nReps=1, extraInfo=exp_info,\n",
" originPath=datapath)\n",
"\n",
"\n",
"#=====================\n",
"# Start the experiment\n",
"#=====================\n",
"\n",
"# Display the start message\n",
"\n",
"# Run through the trials\n",
"for trial in trials:\n",
"# - Wait for a spacebar to start a trial\n",
"# - Read both image files, using:\n",
" trial['image']\n",
" trial['ori']\n",
" \n",
" while no_response and time < timelimit:\n",
"# - Switch the image every 0.5s\n",
"# - Reposition and resize the bubbles\n",
"# - Stop if spacebar has been pressed, or if 30s have passed\n",
"\n",
"# Add the current trial's data to the TrialHandler\n",
" trials.addData('rt', rt)\n",
" trials.addData('acc', acc)\n",
"\n",
"\n",
"#======================\n",
"# End of the experiment\n",
"#======================\n",
"\n",
"# Save all data to a file\n",
"trials.saveAsWideText(data_fname + '.csv', delim=',')\n",
"\n",
"# Quit the experiment"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Useful modules and functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Congratulations, you have now gained the programming knowledge you need to write an experiment. \n",
"\n",
"All you need to learn about now, are useful existing packages, modules and functions which can help you program the experiment. We will review the ones we will need here - but there are many more!"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Global built-in functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Global built-in functions are not part of any package, module or object, but can just be used directly. Apart from those we've seen, like `len()`, the following ones are useful."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# zip() takes two ordered series of equal length\n",
"# ...and creates a list of tuple pairs from their elements\n",
"print zip('haha', 'hihi')\n",
"print zip([1,2,4],[4,5,6])"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[('h', 'h'), ('a', 'i'), ('h', 'h'), ('a', 'i')]\n",
"[(1, 4), (2, 5), (4, 6)]\n"
]
}
],
"prompt_number": 42
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# range() creates a sequence of integers\n",
"# ...by default, starting from 0 and not including the specified integer\n",
"print range(10)\n",
"\n",
"# The (included) starting value can however be specified\n",
"print range(1,10)\n",
"\n",
"# As can the step size\n",
"print range(1,10,2)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
"[1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
"[1, 3, 5, 7, 9]\n"
]
}
],
"prompt_number": 43
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"os"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The os package, built into Python, contains functions that will help you handle directories and files on your computer."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import os\n",
"\n",
"my_file = 'multiplications'\n",
"my_ext = '.py'\n",
"\n",
"# Get the current working directory\n",
"# Returns a string\n",
"my_dir = os.getcwd()\n",
"print my_dir\n",
"\n",
"# Check whether a directory exists\n",
"# Returns a boolean\n",
"print os.path.isdir(my_dir)\n",
"\n",
"# Summarize the files in a directory\n",
"# Returns a list of strings\n",
"print os.listdir(my_dir)\n",
"\n",
"# Creates a directory\n",
"if not os.path.isdir('temp'):\n",
" os.makedirs('temp')\n",
"\n",
"# Joins together different parts of a file path\n",
"# Returns a string\n",
"my_full_path = os.path.join(my_dir, my_file+my_ext)\n",
"print my_full_path\n",
"\n",
"# Check whether a file exists\n",
"# Returns a boolean\n",
"print os.path.exists(my_full_path)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"D:\\Dropbox\\experiments\\change_detection\\2014\n",
"True\n",
"['images', 'python_for_visres.ipynb', 'script_0.py', 'script_1.py', 'script_2.py', 'script_3.py', 'script_final.py']\n",
"D:\\Dropbox\\experiments\\change_detection\\2014\\multiplications.py\n",
"False\n"
]
}
],
"prompt_number": 44
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"numpy.random"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`numpy` is a package that is not by default part of Python, but installed as a user package. It contains many functions useful for science, similar to MATLAB, but here will only use its `random` module, which contains randomization functions."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import numpy.random as rnd\n",
"\n",
"a = [1, 2, 3, 4, 5]\n",
"print a\n",
"\n",
"# Shuffle the order of a\n",
"rnd.shuffle(a)\n",
"print a\n",
"\n",
"# Generate 5 random floats between 0 and 1\n",
"b = rnd.random(5)\n",
"print b\n",
"\n",
"# Generate 5 integers up to but not including 10\n",
"# Also notice how we specify the 'size' argument here\n",
"# It is an OPTIONAL argument; specified by name rather than order\n",
"c = rnd.randint(10, size=5)\n",
"print c"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[1, 2, 3, 4, 5]\n",
"[2, 4, 5, 3, 1]\n",
"[ 0.28133512 0.32693666 0.04976139 0.55204125 0.95322666]\n",
"[1 9 3 1 2]\n"
]
}
],
"prompt_number": 45
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"*Code advancement: 3*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is you final chance to update the code from *Code advancement 2* before we start on PsychoPy!"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"#===============\n",
"# Import modules\n",
"#===============\n",
"\n",
"import os # for file/folder operations\n",
"import numpy.random as rnd # for random number generators\n",
"from psychopy import data\n",
"\n",
"\n",
"#====================================================================\n",
"# Settings that we might want to tweak later on\n",
"#====================================================================\n",
"\n",
"datapath = 'data' # directory to save data in\n",
"impath = 'images' # directory where images can be found\n",
"imlist = ['1','2','3','4','5','6'] # image names (without the suffixes)\n",
"asfx = 'a.jpg' # suffix for the first image\n",
"bsfx = 'b.jpg' # suffix for the second image\n",
"scrsize = (1024,768) # screen size (pixels)\n",
"timelimit = 30 # image freezes after this time (seconds)\n",
"changetime = .5 # how often images are changing (seconds)\n",
"n_bubbles = 40 # number of bubbles overlayed on the image\n",
"\n",
"\n",
"#==========================================\n",
"# Store info about the experiment session\n",
"#==========================================\n",
"\n",
"# Show a dialog box to enter session information\n",
"exp_name = 'Change Detection'\n",
"exp_info = {}\n",
"\n",
"# Get subject name\n",
"# Get date and time\n",
"# Put both in the exp_info dictionary\n",
"\n",
"# Set up filename for saving data\n",
"if not os.path.isdir(datapath):\n",
" os.makedirs(datapath)\n",
"data_fname = exp_info['participant'] + '_' + exp_info['date']\n",
"data_fname = os.path.join(datapath, data_fname)\n",
"\n",
"\n",
"#=========================\n",
"# Prepare conditions lists\n",
"#=========================\n",
"\n",
"# Check if all images exist\n",
"for im in imlist:\n",
" if (not os.path.exists(os.path.join(impath, im+asfx)) or\n",
" not os.path.exists(os.path.join(impath, im+bsfx))):\n",
" raise Exception('Image files not found in image folder: ' + str(im)) \n",
" \n",
"# Randomize the image order\n",
"rnd.shuffle(imlist)\n",
"\n",
"# Make the orientations list, in random order\n",
"# Half upright (0), half inverted (1)\n",
"orilist = [0,1]*(len(imlist)/2)\n",
"rnd.shuffle(orilist)\n",
"\n",
"\n",
"#===============================\n",
"# Creation of window and stimuli\n",
"#===============================\n",
"\n",
"# Open window\n",
"\n",
"# Create the start message\n",
"start_text = 'Press space to start the experiment'\n",
"\n",
"# Define stimuli (contents can still change)\n",
"# Define bubbles (positions and sizes can still change)\n",
"\n",
"\n",
"#=========================\n",
"# Creation of TrialHandler\n",
"#=========================\n",
"\n",
"stim_order = []\n",
"for im, ori in zip(imlist, orilist):\n",
" stim_order.append({'im': im, 'ori': ori})\n",
"\n",
"trials = data.TrialHandler(stim_order, nReps=1, extraInfo=exp_info,\n",
" method='sequential', originPath=datapath)\n",
"\n",
"\n",
"#=====================\n",
"# Start the experiment\n",
"#=====================\n",
"\n",
"# Display the start message\n",
"\n",
"# Run through the trials\n",
"for trial in trials:\n",
"# - Wait for a spacebar to start a trial\n",
"# - Read both image files, using:\n",
" im_fname = os.path.join(impath, trial['image'])\n",
" trial['ori']\n",
" \n",
" while no_response and time < timelimit:\n",
"# - Switch the image every 0.5s\n",
"# - Reposition and resize the bubbles\n",
"# - Stop if spacebar has been pressed, or if 30s have passed\n",
"\n",
"# Add the current trial's data to the TrialHandler\n",
" trials.addData('rt', rt)\n",
" trials.addData('acc', acc)\n",
"\n",
"# Save data to a file, and quit the experiment\n",
"trials.saveAsWideText(data_fname + '.csv', delim=',')\n",
"core.quit()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"PsychoPy"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"PsychoPy is a package which implements for you a bunch of useful functions for running psychological experiments, divided into several modules:\n",
"\n",
"* **core**: various functions\n",
"* **data**: handles condition parameters, response registration, and order of trials\n",
"* **visual**: handles the drawing of stimuli to the screen\n",
"* **event**: handles keyboard input\n",
"* **gui**: handles the creation of dialog boxes\n",
"* ...and more, but we will not cover them here\n",
"\n",
"These modules will allow us to implement the missing components:\n",
"\n",
"* input dialog\n",
"* how to create a window\n",
"* how to define stimuli\n",
"* how to draw stimuli on screen\n",
"* recording keyboard inputs\n",
"* actual trial loop"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Input dialog"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When you run an experiment, the first thing you probably want to do is to collect some information about the participant, such as participant ID or session number. PsychoPy provides a very simple interface for that via its [gui module](http://www.psychopy.org/api/gui.html):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import gui\n",
"\n",
"exp_name = 'Change detection'\n",
"exp_info = {'participant': ''}\n",
"gui.DlgFromDict(dictionary=exp_info, title=exp_name)\n",
"\n",
"print exp_info"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice how the `exp_info` variable gets updated after you enter information in the Dialog Box.\n",
"\n",
"Now try rerunning that code snippet again, provide the participant ID again, but now click *Cancel*. What happens to `exp_info`? You'll see that `exp_info` is still updated. This is not a desired outcome so we need to improve our code to quit the experiment if *Cancel* is clicked."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import gui\n",
"\n",
"exp_name = 'Change detection'\n",
"exp_info = {'participant': ''}\n",
"dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)\n",
"\n",
"if dlg.OK == False:\n",
" core.quit() # user pressed cancel, so we quit"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since `exp_info` is a simple *dict*, you can store more information in it asking a participant to provide it, e.g.:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"exp_info['exp_name'] = exp_name"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You may also want to record **date and time** when the experiment took place. PsychoPy provides a simple date stamp in its [data module](http://www.psychopy.org/api/data.html):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import data\n",
"\n",
"exp_info['date'] = data.getDateStr()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Window"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All stimuli in your experiment will be displayed on a single **window**. You could have more windows if you like (for a stereo setup, for example) but usually we only need one window which is defined by a [Window class](http://www.psychopy.org/api/visual/window.html) in PsychoPy's [visual module](http://www.psychopy.org/api/visual.html). Below is how you open a window but don't run this cell:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual\n",
"\n",
"visual.Window() # don't run this line"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Had you gone ahead and opened a window right now, you would have see it wouldn't close nicely. In fact, given its purpose to show stimuli, the window is expected to be created, displayed for a certain period of time, and then close. To define its duration, we can use the **wait** function for PsychoPy's [core module](http://www.psychopy.org/api/core.html). After this time is up, we simply **close** the window:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual, core\n",
"\n",
"win = visual.Window()\n",
"core.wait(3) # seconds\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Windows have several parameters that we can manipulate. Try running the following examples and observed what is changing."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"win = visual.Window(size=(1024,768), color='white')\n",
"core.wait(3)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"win = visual.Window(fullscr=True)\n",
"core.wait(3)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"One final useful parameter is `units` which controls the units you use to define stimuli size. In this experiment, we simply pass `units='pix'` which means that (by default) you define stimulus size in pixels. (If you fail to define units, their may be problems later in drawing stimuli.) However, often we want to define stimuli in terms of their size in degrees visual angle. In that case, we would give `units='deg'` to the Window class. This not sufficient though because PsychoPy needs to know the size of your screen and participant's distance from it. These parameters are stored in the [Monitor Center](http://www.psychopy.org/general/monitors.html) and can be accessed by creating a monitor. We will not cover this procedure here but you can [read more about units here](http://www.psychopy.org/general/units.html#units)."
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Stimuli"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"PsychoPy allows to draw many kinds of stimuli: geometric shapes, images, gratings, element arrays, text, and even movies. You can see all possibilities listed in the [visual module](http://www.psychopy.org/api/visual.html); in this tutorial, we will draw [images](http://www.psychopy.org/api/visual/simpleimagestim.html), [text](http://www.psychopy.org/api/visual/textstim.html) and [circles](http://www.psychopy.org/api/visual/circle.html).\n",
"\n",
"In general, you first create a number of stimuli objects, and as you go into experimental loop, their properties are manipulated. Note how this is a different strategy from creating on object per stimulus we want to show. In our experiment, for example, we have 12 images to show. We could create 12 image objects, each storing a different image, but that's inefficient and inelegant. In fact, we only need two bitmap objects defined upfront, and we will be updating which image is shown by updating image file path of these two bitmaps."
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Image"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's draw an image on our window. Images are displayed using [SimpleImageStim](http://www.psychopy.org/api/visual/simpleimagestim.html) At the very least, you need to supply two arguments to create an image instance: the **window** where the image has to be drawn and the **path** to the image file:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual, core\n",
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"bitmap = visual.SimpleImageStim(win, 'images/1a.jpg')\n",
"core.wait(3)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But where is the image? The thing is that the `bitmap` object was created but it has yet to be **drawn** on the window for us to see."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"bitmap = visual.SimpleImageStim(win, 'images/1a.jpg')\n",
"bitmap.draw()\n",
"core.wait(3)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Still nothing... Here is one more thing to learn: stimuli are drawn on the *back* buffer and this buffer screen needs to be brought to the front to be seen. Think about it as always having two screens: *front* (the one you a currently seeing) and *back* (the one drawn in the background and not yet seen). What you want to do is to **flip** the back screen up front, which is conveniently done using `win.flip()` command.\n",
"\n",
"*(Wondering why we need these two buffers? The idea is to increase the performance. Drawing stimuli might take a long time but flipping between drawn screens is fast. Here PsychoPy cleverly allows you to draw next stimuli while the current ones are still on the screen.)*"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"bitmap = visual.SimpleImageStim(win, 'images/1a.jpg')\n",
"bitmap.draw() # draw bitmap on the window\n",
"win.flip() # make bitmap visible\n",
"core.wait(3)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Geometric shapes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can draw various geometric shapes using PsychoPy's [ShapeStim class](http://www.psychopy.org/api/visual/shapestim.html). However, several common shapes are available immediately; here is an example of drawing a black [disk](http://www.psychopy.org/api/visual/circle.html) which we need for our experiment:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"bubble = visual.Circle(win, fillColor='black', lineColor='black', radius=30)\n",
"bubble.draw()\n",
"win.flip()\n",
"core.wait(3) # seconds\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Text"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Text stimulus is created using the [TextStim class](http://www.psychopy.org/api/visual/textstim.html). It behaves just like other stimuli but, of course, font size and so on can be manipulated:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"text = visual.TextStim(win, text='Press space to start the experiment', color='red', height=100)\n",
"text.draw()\n",
"win.flip()\n",
"core.wait(3) # seconds\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Keyboard input"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that we have a text stimulus on the screen that says \"Press space to start the experiment\", let's learn how to register user input. PsychoPy provides a simple interface to wait for a key press in the [event module](http://www.psychopy.org/api/event.html):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual, core, event\n",
"\n",
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"text = visual.TextStim(win, text='Press space to start the experiment', color='red', height=100)\n",
"text.draw()\n",
"win.flip()\n",
"\n",
"keypress = event.waitKeys(keyList=['space', 'escape'])\n",
"print keypress\n",
"if keypress[0] == 'escape':\n",
" win.close()\n",
"else:\n",
" print 'Start of experiment'\n",
" win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Running experiment"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Dynamic manipulation of stimuli properties"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In our experiment, images are changing once in a while. To update which image is shown, we use `setImage` command:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual, core\n",
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
" \n",
"bitmap = visual.SimpleImageStim(win, 'images/1a.jpg')\n",
"bitmap.draw() # draw bitmap on the window\n",
"win.flip() # make bitmap visible\n",
"core.wait(3)\n",
"\n",
"bitmap.setImage('images/1b.jpg')\n",
"bitmap.draw()\n",
"win.flip()\n",
"\n",
"core.wait(3)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"During the trial"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we actually want to have the images flip back and forth continuously, so we need a **loop**:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual, core\n",
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"bitmap = visual.SimpleImageStim(win, 'images/1a.jpg')\n",
"\n",
"for i in range(5):\n",
" bitmap.setImage('images/1a.jpg')\n",
" bitmap.draw() # draw bitmap on the window\n",
" win.flip() # make bitmap visible\n",
" core.wait(.5)\n",
"\n",
" bitmap.setImage('images/1b.jpg')\n",
" bitmap.draw()\n",
" win.flip()\n",
" core.wait(.5)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, notice that we are not very efficient here: many commands are implemented twice. We can do better, and should always strive for more elegance in Python:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual, core\n",
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"bitmap = visual.SimpleImageStim(win, 'images/1a.jpg')\n",
"\n",
"for i in range(10): # note that we now need 6, not 3 iterations\n",
" # change the bitmap\n",
" if bitmap.image == 'images/1a.jpg':\n",
" bitmap.setImage('images/1b.jpg')\n",
" else:\n",
" bitmap.setImage('images/1a.jpg')\n",
" bitmap.draw()\n",
" win.flip()\n",
" core.wait(.5)\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
" Get participant input"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So far we had the loop continue for a fixed number of iterations. However, the actual experiment requires participants to hit a space bar to indicate that they saw a change. meaning that the stimuli have to be flipping until the response and not for a fixed number of times. So that's a **while** loop. Also, the trial should stop after 30 seconds if no response was made. Thus, we need to implement two things: input registration and clocks to count time.\n",
"\n",
"For input registration, remember that we used **event.waitKeys** before. We can try to use it here as well.\n",
"\n",
"For timing, PsychoPy provides a **core.Clock** class. You can have as many clocks as you like and start them with a *clock.reset()* command. In our case, we can start a clock right away and when it reaches the time limit, just exit the while loop.\n",
"\n",
"Putting these ideas together we arrive to the following code:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from psychopy import visual, core, event\n",
"win = visual.Window(size=(1024,768), color='white', units='pix')\n",
"bitmap = visual.SimpleImageStim(win, 'images/1a.jpg')\n",
"\n",
"# Initialize clock to register response time\n",
"rt_clock = core.Clock()\n",
"rt_clock.reset() # start rt clock\n",
"done = False\n",
"\n",
"# Start the trial\n",
"keys = None\n",
"while keys is None and rt_clock.getTime() < 30:\n",
" # Change the bitmap\n",
" if bitmap.image == 'images/1a.jpg':\n",
" bitmap.setImage('images/1b.jpg')\n",
" else:\n",
" bitmap.setImage('images/1a.jpg')\n",
" bitmap.draw()\n",
" \n",
" # Show the new screen we've drawn\n",
" win.flip()\n",
" \n",
" # For 500 ms, do nothing except wait for the participant's response\n",
" keys = event.waitKeys(maxWait=.5, keyList=['space', 'escape'], timeStamped=rt_clock)\n",
" print keys\n",
"win.close()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note how we added `timeStamped=rt_clock` in `event.waitKeys`. The effect is that the returned keys also contain time when they were pressed according to the particular clock we supplied.\n",
"\n",
"There is also an alternative for recording user inputs using a while-loop:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"change_clock = core.Clock()\n",
"\n",
"keys = [] # this list will fill up once a participant responds\n",
"while len(keys) == 0 and rt_clock.getTime() < 30:\n",
" ...\n",
" # For 500 ms, do nothing except wait for the participant's response\n",
" change_clock.reset()\n",
" while change_clock.getTime() < .5:\n",
" # Register spacebar or escape press\n",
" keys = event.getKeys(keyList=['space', 'escape'])\n",
" if len(keys) > 0:\n",
" break"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This approack is useful in scenarios where trials occur non-stop, i.e. there is no waiting for user response. For example, in fMRI experiments, trial onsets must be time-locked such that their onsets are in sync with scanner. In such paradigms we want to make sure that a trial ends just in time. Now you may think that it does so with **event.waitKeys** too (since we set `maxWait=.5`) but the harsh truth is that this waiting starts only *after* stimuli are drawn. So if it took the computer 20 ms to draw the stimuli, the total trial duration will be 520 ms instead of 500 ms. These tiny offsets accumulate quickly so one idea is to constantly check trial and global timing using these while-loops and end as soon as necessary as counted from the *beginning* of a trial.\n",
"\n",
"I find the while-loop approach to be more robust so in our experiment we use this method."
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Draw bubbles"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we also need to put some bubbles on top of the image. Also, these bubbles should be changing their size and positions, so here is the bit that controls these parameters (you'll have to insert that in the while loop in the snippet above if you want to test it):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Draw bubbles at new random positions \n",
"for radius in range(40):\n",
" bubble.setRadius(radius)\n",
" bubble.setPos(((rnd.random()-.5) * scrsize[0],\n",
" (rnd.random()-.5) * scrsize[1] ))\n",
" bubble.draw()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"End of trial"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"At the end of a trial, we check if the participant actually responded (the `keys` lsit shouldn't be empty). If not, that means the time limit was reached."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Check if participant responded\n",
"if len(keys) > 0:\n",
" # Was it an escape button?\n",
" if 'escape' in keys:\n",
" core.quit()\n",
" else:\n",
" # Take only the last key press\n",
" last_key = keys[-1]\n",
" rt = keys[-1][1]\n",
" acc = keys[-1][0]\n",
"else:\n",
" rt = timelimit\n",
" acc = 0 # no response is an incorrect response"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Trial loop"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we have to wrap this into a trial loop:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Display the start message\n",
"start_message.draw()\n",
"win.flip()\n",
"\n",
"# Start the main loop that goes through all trials\n",
"for trial in trials:\n",
" \n",
" # Wait for spacebar press to start (or escape to quit)\n",
" keys = event.waitKeys(keyList=['space', 'escape'])\n",
" if 'escape' in keys:\n",
" core.quit()\n",
"\n",
" # Fetch the image path (minus the suffix)\n",
" im_fname = os.path.join(impath, trial['im'])\n",
" \n",
" # Flip image upside down if necessary \n",
" bitmap.setFlipHoriz(trial['ori'])\n",
" \n",
" # Show stimuli, collect responses\n",
" # ..."
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"*Code advancement: Final*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Below is the full code for the experiment with the discussed PsychoPy functions added to the *Code advancement 3*"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"#===============\n",
"# Import modules\n",
"#===============\n",
"\n",
"import os # for file/folder operations\n",
"import numpy.random as rnd # for random number generators\n",
"from psychopy import visual, event, core, gui, data\n",
"\n",
"\n",
"#==============================================\n",
"# Settings that we might want to tweak later on\n",
"#==============================================\n",
"\n",
"datapath = 'data' # directory to save data in\n",
"impath = 'images' # directory where images can be found\n",
"imlist = ['1','2','3','4','5','6'] # image names (without the suffixes)\n",
"asfx = 'a.jpg' # suffix for the first image\n",
"bsfx = 'b.jpg' # suffix for the second image\n",
"scrsize = (1024,768) # screen size (pixels)\n",
"timelimit = 30 # image freezes after this time (seconds)\n",
"changetime = .5 # how often images are changing (seconds)\n",
"n_bubbles = 40 # number of bubbles overlayed on the image\n",
"\n",
"\n",
"#========================================\n",
"# Store info about the experiment session\n",
"#========================================\n",
"\n",
"# Show a dialog box to enter session information\n",
"exp_name = 'Change Detection'\n",
"exp_info = {\n",
" 'participant': '', \n",
" 'gender': ('male', 'female'), \n",
" 'age':'', \n",
" 'left-handed':False \n",
" }\n",
"dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)\n",
"\n",
"# If 'Cancel' is pressed, quit\n",
"if dlg.OK == False:\n",
" core.quit()\n",
"\n",
"# Add the date and the experiment name\n",
"exp_info['date'] = data.getDateStr()\n",
"exp_info['exp_name'] = exp_name\n",
"\n",
"# Set up filename for saving data\n",
"if not os.path.isdir(datapath):\n",
" os.makedirs(datapath)\n",
"data_fname = exp_info['participant'] + '_' + exp_info['date']\n",
"data_fname = os.path.join(datapath, data_fname)\n",
"\n",
"\n",
"#=========================\n",
"# Prepare conditions lists\n",
"#=========================\n",
"\n",
"# Check if all images exist\n",
"for im in imlist:\n",
" if (not os.path.exists(os.path.join(impath, im+asfx)) or\n",
" not os.path.exists(os.path.join(impath, im+bsfx))):\n",
" raise Exception('Image files not found in image folder: ' + str(im)) \n",
" \n",
"# Randomize the image order\n",
"rnd.shuffle(imlist)\n",
"\n",
"# Make the orientations list, in random order\n",
"# Half upright (0), half inverted (1)\n",
"orilist = [0,1]*(len(imlist)/2)\n",
"rnd.shuffle(orilist)\n",
"\n",
"\n",
"#===============================\n",
"# Creation of window and stimuli\n",
"#===============================\n",
"\n",
"# Open a window\n",
"win = visual.Window(size=scrsize, color='white', units='pix', fullscr=False)\n",
"\n",
"# Create the start message\n",
"start_message = visual.TextStim(win,\n",
" text=\"Press space to start the experiment\",\n",
" color='red', height=20)\n",
"\n",
"# Create the bitmap object; its contents can still change\n",
"bitmap = visual.SimpleImageStim(win, \n",
" image=os.path.join(impath, imlist[0]+asfx))\n",
"\n",
"# Create the bubble object\n",
"bubble = visual.Circle(win, fillColor='black', lineColor='black')\n",
"\n",
"\n",
"#=========================\n",
"# Creation of TrialHandler\n",
"#=========================\n",
"\n",
"stim_order = []\n",
"for im, ori in zip(imlist, orilist):\n",
" stim_order.append({'im': im, 'ori': ori})\n",
"\n",
"trials = data.TrialHandler(stim_order, nReps=1, extraInfo=exp_info,\n",
" method='sequential', originPath=datapath)\n",
"\n",
"\n",
"#=====================\n",
"# Start the experiment\n",
"#=====================\n",
"\n",
"# Display the start message\n",
"start_message.draw()\n",
"win.flip()\n",
"\n",
"# Initialize two clocks:\n",
"# - for image change time\n",
"# - for response time\n",
"change_clock = core.Clock()\n",
"rt_clock = core.Clock()\n",
"\n",
"# Start the main loop that goes through all trials\n",
"for trial in trials:\n",
" \n",
" # Wait for spacebar press to start (or escape to quit)\n",
" keys = event.waitKeys(keyList=['space', 'escape'])\n",
" if 'escape' in keys:\n",
" core.quit()\n",
"\n",
" # Fetch the image path (minus the suffix)\n",
" im_fname = os.path.join(impath, trial['im'])\n",
" \n",
" # Flip image upside down if necessary \n",
" bitmap.setFlipHoriz(trial['ori'])\n",
"\n",
" # Set the reaction time clock to 0\n",
" rt_clock.reset()\n",
"\n",
" # Start the trial\n",
" keys = [] # this list will fill up once a participant responds\n",
" while len(keys) == 0 and rt_clock.getTime() < timelimit:\n",
"\n",
" # Change the bitmap\n",
" if bitmap.image == im_fname + asfx:\n",
" bitmap.setImage(im_fname + bsfx)\n",
" else:\n",
" bitmap.setImage(im_fname + asfx)\n",
" bitmap.draw()\n",
"\n",
" # Draw bubbles at new random positions \n",
" for radius in range(n_bubbles):\n",
" bubble.setRadius(radius)\n",
" bubble.setPos(((rnd.random()-.5) * scrsize[0],\n",
" (rnd.random()-.5) * scrsize[1] ))\n",
" bubble.draw()\n",
"\n",
" # Show the new screen we've drawn\n",
" win.flip()\n",
" \n",
" # For 500 ms, do nothing except wait for the participant's response\n",
" change_clock.reset()\n",
" while change_clock.getTime() <= changetime:\n",
" \n",
" # Register spacebar or escape press\n",
" keys = event.getKeys(keyList=['space','escape'])\n",
" if len(keys) > 0:\n",
" break\n",
"\n",
" # Check if participant responded\n",
" if len(keys) > 0:\n",
" \n",
" # Was it an escape button?\n",
" if 'escape' in keys:\n",
" core.quit()\n",
" else:\n",
" # Take only the last key press\n",
" last_key = keys[-1]\n",
" rt = keys[-1][1]\n",
" acc = keys[-1][0]\n",
" else:\n",
" rt = timelimit\n",
" acc = 0 # no response is an incorrect response\n",
" \n",
" # Add the current trial's data to the TrialHandler\n",
" trials.addData('rt', rt)\n",
" trials.addData('acc', acc)\n",
" \n",
" \n",
"#======================\n",
"# End of the experiment\n",
"#======================\n",
"\n",
"# Save all data to a file\n",
"trials.saveAsWideText(data_fname + '.csv', delim=',')\n",
"\n",
"# Quit the experiment\n",
"core.quit()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## "
]
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment