{ "metadata": { "name": "", "signature": "sha256:f6576705da860985fb517dfd4502c162082bb9295dde3e37bddf6c4c2b9f4d60" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "*Back to the main [index](../index.ipynb)*" ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "An introduction to Python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Part of the introductory series to using [Python for Vision Research](http://gestaltrevision.be/wiki/python/python) brought to you by the [GestaltReVision](http://gestaltrevision.be) group (KU Leuven, Belgium).*\n", "\n", "In the first two parts you will learn the basics of Python just enough to build a real working experiment. \n", "\n", "**Authors:** Maarten Demeyer, [Jonas Kubilius](http://klab.lt) \n", "**Year:** 2014 \n", "**Copyright:** Public Domain as in [CC0](https://creativecommons.org/publicdomain/zero/1.0/)" ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Contents" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [Code advancement 0](#Code-advancement-0)\n", "- [Literals, expressions, and variables](#Literals,-expressions,-and-variables)\n", "- [Statements and program flow](#Statements-and-program-flow)\n", "- [More data types](#More-data-types)\n", "- [More program flow](#More-program-flow)\n", "- [Debugging](#Debugging)\n", "- [Intermediate summary](#Intermediate-summary)\n", "- [Code advancement 1](#Code-advancement-1)\n", "- [Functions](#Functions)\n", "- [Modules](#Modules)\n", "- [Objects](#Objects)\n", "- [Example: The PsychoPy TrialHandler](#Example:-The-PsychoPy-TrialHandler)\n", "- [Intermediate summary](#Intermediate-summary)\n", "- [Code advancement 2](#Code-advancement-2)\n", "- [Useful modules and functions](#Useful-modules-and-functions)\n", " - [Global built-in functions](#Global-built-in-functions)\n", " - [os](#os)\n", " - [numpy.random](#numpy.random)\n", "- [Code advancement 3](#Code-advancement-3)" ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Code advancement 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The goal of the first two classes is to build a *Change Blindness* experiment. We will work with pairs of images that only differ on a tiny detail, for example, a green traffic light might turn into red in a typical street scene. We will continuously switch between both images until the participant observes a difference between the two. In addition, half of the image pairs will be presented upright, whereas half will be inverted. To make the changes more difficult to detect, we will have bubbles of various sizes superimposed on the images, which will be repositioned to new random locations upon each image switch. When the participant detects the change, she should hit spacebar. Otherwise, after 30 seconds the experiment advances to the next stimulus. At any time, the participant can press escape to quit the experiment.\n", "\n", "If this is not very clear, find the full code of the experiment in `script_final.py` in the `Part2` directory, 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": [ "%load script_0.py" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 5 }, { "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 images can be found image\n", "# image names without the suffixes 1,2,3,4,5,6\n", "# suffix for the first image a.jpg\n", "# suffix for the second image b.jpg\n", "# screen size in pixels 1200x800\n", "# image freezing time in seconds 30\n", "# image changing time in seconds 0.5\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 through a dialog box\n", "# If 'Cancel' is pressed, quit\n", "# Get date and time\n", "# Store this information as general session info\n", "# Create a unique filename for the experiment data\n", "\n", "\n", "#========================\n", "# Prepare condition lists\n", "#========================\n", "\n", "# Check if all images exist\n", "# Randomize the image order\n", "\n", "# Create the orientations list: half upright, half inverted\n", "# Randomize the orientation order\n", "\n", "\n", "#===============================\n", "# Creation of window and stimuli\n", "#===============================\n", "\n", "# Open a window\n", "# Define trial start text\n", "# Define the bitmap stimuli (contents can still change)\n", "# Define a bubble (position and size can still change)\n", "\n", "\n", "#==========================\n", "# Define the trial sequence\n", "#==========================\n", "\n", "# Define a list of trials with their properties:\n", "# - Which image (without the suffix)\n", "# - Which orientation\n", "\n", "\n", "#=====================\n", "# Start the experiment\n", "#=====================\n", "\n", "# Run through the trials. On each trial:\n", "# - Display trial start text\n", "# - Wait for a spacebar press to start the trial, or escape to quit\n", "# - Set the images, set the orientation\n", "# - Switch the image every 0.5s, and:\n", "# - Draw bubbles of increasing radius at random positions\n", "# - Listen for a spacebar or escape press\n", "# - Stop trial if spacebar or escape has been pressed, or if 30s have passed\n", "# - Analyze the keypress\n", "# - Escape press = quit the experiment\n", "# - Spacebar press = correct change detection; register response time\n", "# - No press = failed change detection; maximal response time\n", "# - Advance to the next trial\n", "\n", "\n", "#======================\n", "# End of the experiment\n", "#======================\n", "\n", "# Save all data to a file\n", "# Quit the experiment\n" ], "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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\n", "\n", "# Similarly, '*' performs replication of the entire string\n", "print a * 2" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "# Same goes for a tuple (or a list)\n", "b = (1, 2.0, True)\n", "print b + (5,)\n", "print b * 2\n", "print b\n", "\n", "# By re-assigning, we do change the tuple\n", "b = b + (5,)\n", "print b" ], "language": "python", "metadata": {}, "outputs": [] }, { "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": [] }, { "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": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another handy fact is that an **empty string, list, tuple, or dictionary will by itself evaluate to `False`**, if a boolean is required. There is no need to check whether the length is equal to zero. Play around with the variable `a` to check that we're telling you no lies. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "a = []\n", "\n", "# We could do:\n", "if len(a) == 0:\n", " print 'This variable is empty!'\n", "else:\n", " print 'There is something in here!'\n", "\n", "# But this is shorter:\n", "if not a:\n", " print 'This variable is empty!'\n", "else:\n", " print 'There is something in here!'" ], "language": "python", "metadata": {}, "outputs": [] }, { "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": [] }, { "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": [] }, { "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": [ "number_of_paws = -1\n", "\n", "if number_of_paws < 0:\n", " raise Exception('Your dog cannot have a negative number of paws!')\n", "\n", "print 'My dog has', number_of_paws, 'paw(s).'" ], "language": "python", "metadata": {}, "outputs": [] }, { "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": [] }, { "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": [] }, { "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 at any time.\n", "* To stop and debug a script during its execution, use the built-in **`pdb`** module. There is a nice introducion to `pdb` on [our wiki](http://gestaltrevision.be/wiki/python/debugging). In Spyder, `pdb` breakpoints can be set visually by double clicking on the relevant line number. A red dot will appear, where the script will stop if you run it in debug mode (CTRL-F5, then CTRL-F12). In combination with Spyder's Variable Explorer, this makes for a very powerful debugging method." ] }, { "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", "- len()==0 is unnecessary" ] }, { "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": [ "%load script_1.py" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 4 }, { "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 = (1200,800) # screen size in pixels\n", "timelimit = 30 # image freezing time in seconds\n", "changetime = .5 # image changing time in 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", "exp_name = 'Change Detection'\n", "exp_info = {}\n", "\n", "# Get subject name, gender, age, handedness through a dialog box\n", "# If 'Cancel' is pressed, quit\n", "# Get date and time\n", "# Store this information as general session info\n", "\n", "# Create a unique filename for the experiment data\n", "data_fname = exp_info['participant'] + '_' + exp_info['date']\n", "\n", "\n", "#========================\n", "# Prepare condition lists\n", "#========================\n", "\n", "# Check if all images exist\n", "# Randomize the image order\n", "\n", "# Create the orientations list: half upright, half inverted\n", "orilist = [0,1]*(len(imlist)/2)\n", "\n", "# Randomize the orientation order\n", "\n", "\n", "#===============================\n", "# Creation of window and stimuli\n", "#===============================\n", "\n", "# Open a window\n", "\n", "# Define trial start text\n", "text = \"Press spacebar to start the trial\"\n", "\n", "# Define the bitmap stimuli (contents can still change)\n", "# Define a bubble (position and size can still change)\n", "\n", "\n", "#==========================\n", "# Define the trial sequence\n", "#==========================\n", "\n", "# Define a list of trials with their properties:\n", "# - Which image (without the suffix)\n", "# - Which orientation\n", "\n", "\n", "#=====================\n", "# Start the experiment\n", "#=====================\n", "\n", "for trial in trials:\n", " \n", " # Display trial start text\n", " \n", " # Wait for a spacebar press to start the trial, or escape to quit\n", " \n", " # Set the image filename, set the orientation \n", " \n", " # Start the trial\n", " # Stop trial if spacebar or escape has been pressed, or if 30s have passed\n", " while not response and time < timelimit:\n", " \n", " # Switch the image\n", " \n", " # Draw bubbles of increasing radius at random positions\n", "\n", " # For the duration of 'changetime',\n", " # Listen for a spacebar or escape press\n", " while time < changetime:\n", " if response:\n", " break\n", "\n", " # Analyze the keypress\n", " if response:\n", " if escape_pressed:\n", " # Escape press = quit the experiment\n", " break\n", " elif spacebar_pressed:\n", " # Spacebar press = correct change detection; register response time\n", " else:\n", " # No press = failed change detection; maximal response time\n", "\n", " # Advance to the next trial\n", "\n", "\n", "#======================\n", "# End of the experiment\n", "#======================\n", "\n", "# Save all data to a file\n", "# Quit the experiment" ], "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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 (without the .py extension). 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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [] }, { "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": [ "%load script_2.py" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 3 }, { "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 = (1200,800) # screen size in pixels\n", "timelimit = 30 # image freezing time in seconds\n", "changetime = .5 # image changing time in 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", "exp_name = 'Change Detection'\n", "exp_info = {}\n", "\n", "# Get subject name, gender, age, handedness through a dialog box\n", "# If 'Cancel' is pressed, quit\n", "# Get date and time\n", "# Store this information as general session info\n", "\n", "# Create a unique filename for the experiment data\n", "data_fname = exp_info['participant'] + '_' + exp_info['date']\n", "\n", "\n", "#========================\n", "# Prepare condition lists\n", "#========================\n", "\n", "# Check if all images exist\n", "# Randomize the image order\n", "\n", "# Create the orientations list: half upright, half inverted\n", "orilist = [0,1]*(len(imlist)/2)\n", "\n", "# Randomize the orientation order\n", "\n", "\n", "#===============================\n", "# Creation of window and stimuli\n", "#===============================\n", "\n", "# Open a window\n", "\n", "# Define trial start text\n", "text = \"Press spacebar to start the trial\"\n", "\n", "# Define the bitmap stimuli (contents can still change)\n", "# Define a bubble (position and size can still change)\n", "\n", "\n", "#==========================\n", "# Define the trial sequence\n", "#==========================\n", "\n", "# Define a list of trials with their properties:\n", "# - Which image (without the suffix)\n", "# - Which orientation\n", "\n", "# We need a list of dictionaries, e.g. {im:'6',ori:1}\n", "# Leave blank, we will soon learn how to fill this with imlist and orilist\n", "stim_order = [{},{},{},{},{},{}]\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", "for trial in trials:\n", " \n", " # Display trial start text\n", " \n", " # Wait for a spacebar press to start the trial, or escape to quit\n", " \n", " # Set the images, set the orientation \n", " trial['im']\n", " trial['ori']\n", " \n", " # Start the trial\n", " # Stop trial if spacebar or escape has been pressed, or if 30s have passed\n", " while not response and time < timelimit:\n", " \n", " # Switch the image\n", " \n", " # Draw bubbles of increasing radius at random positions\n", "\n", " # For the duration of 'changetime',\n", " # Listen for a spacebar or escape press\n", " while time < changetime:\n", " if response:\n", " break\n", "\n", " # Analyze the keypress\n", " if response:\n", " if escape_pressed:\n", " # Escape press = quit the experiment\n", " break\n", " elif spacebar_pressed:\n", " # Spacebar press = correct change detection; register response time\n", " acc = 1\n", " rt =\n", " else:\n", " # No press = failed change detection; maximal response time\n", " acc = 0\n", " rt = timelimit\n", " \n", " # Add the current trial's data to the TrialHandler\n", " trials.addData('rt', rt)\n", " trials.addData('acc', acc)\n", "\n", " # Advance to the next trial\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": [] }, { "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": [] }, { "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", "# 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": [] }, { "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": [] }, { "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": [ "%load script_3.py" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "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 = (1200,800) # screen size in pixels\n", "timelimit = 30 # image freezing time in seconds\n", "changetime = .5 # image changing time in 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", "exp_name = 'Change Detection'\n", "exp_info = {}\n", "\n", "# Get subject name, gender, age, handedness through a dialog box\n", "# If 'Cancel' is pressed, quit\n", "# Get date and time\n", "# Store this information as general session info\n", "\n", "# Create a unique filename for the experiment 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", "# Create the orientations list: half upright, half inverted\n", "orilist = [0,1]*(len(imlist)/2)\n", "\n", "# Randomize the orientation order\n", "rnd.shuffle(orilist)\n", "\n", "\n", "#===============================\n", "# Creation of window and stimuli\n", "#===============================\n", "\n", "# Open a window\n", "\n", "# Define trial start text\n", "text = \"Press spacebar to start the trial\"\n", "\n", "# Define the bitmap stimuli (contents can still change)\n", "# Define a bubble (position and size can still change)\n", "\n", "\n", "#==========================\n", "# Define the trial sequence\n", "#==========================\n", "\n", "# Define a list of trials with their properties:\n", "# - Which image (without the suffix)\n", "# - Which orientation\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", "for trial in trials:\n", " \n", " # Display trial start text\n", " \n", " # Wait for a spacebar press to start the trial, or escape to quit\n", " \n", " # Set the images, set the orientation\n", " im_fname = os.path.join(impath, trial['im'])\n", " trial['ori']\n", " \n", " # Empty the keypresses list\n", " keys = [] \n", " \n", " # Start the trial\n", " # Stop trial if spacebar or escape has been pressed, or if 30s have passed\n", " while not response and time < timelimit:\n", " \n", " # Switch the image\n", " \n", " # Draw bubbles of increasing radius at random positions\n", " for radius in range(n_bubbles): \n", " radius/2.\n", " pos = ((rnd.random()-.5) * scrsize[0],\n", " (rnd.random()-.5) * scrsize[1] )\n", "\n", " # For the duration of 'changetime',\n", " # Listen for a spacebar or escape press\n", " while time < changetime:\n", " if response:\n", " break\n", "\n", " # Analyze the keypress\n", " if response:\n", " if escape_pressed:\n", " # Escape press = quit the experiment\n", " break\n", " elif spacebar_pressed:\n", " # Spacebar press = correct change detection; register response time\n", " acc = 1\n", " rt\n", " else:\n", " # No press = failed change detection; maximal response time\n", " acc = 0\n", " rt = timelimit\n", " \n", " # Add the current trial's data to the TrialHandler\n", " trials.addData('rt', rt)\n", " trials.addData('acc', acc)\n", "\n", " # Advance to the next trial\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": [] } ], "metadata": {} } ] }