{ "metadata": { "name": "", "signature": "sha256:170bee67d77c5e68a593504c29027fb0b020455b9648f51b8837c215c15a2fd6" }, "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": [ "Introduction to PsychoPy for creating experiments" ] }, { "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 this part we will capitalize on the basics you learned in Part 1 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/) (images used in this part are thanks to [Unsplash](http://unsplash.com/) but are in Public Domain too)" ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Contents" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [Introduction](#Introduction)\n", "- [What you need](#What-you-need)\n", "- [Input dialog](#Input-dialog)\n", "- [Window](#Window)\n", "- [Stimuli](#Stimuli)\n", " - [Image](#Image)\n", " - [Geometric shapes](#Geometric-shapes)\n", " - [Text](#Text)\n", " - [Updating-stimulus-properties](#Updating-stimulus-properties)\n", "- [Keyboard input](#Keyboard-input)\n", "- [Timing](#Timing)\n", " - [Clock-based timing](#Clock-based-timing)\n", " - [Frame-based timing](#Frame-based-timing)\n", " - [Which one to use?](#Which-one-to-use?)\n", "- [Running experiment](#Running-experiment)\n", " - [During the trial](#During-the-trial)\n", " - [Get participant input and response time](#Get-participant-input-and-response-time)\n", " - [Draw bubbles](#Draw-bubbles)\n", " - [End of trial](#End-of-trial)\n", " - [Trial loop](#Trial-loop)\n", "- [Code advancement: Final](#Code-advancement:-Final)" ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[PsychoPy](http://www.psychopy.org) 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", "* Show the session info input dialog\n", "* Create a window\n", "* Define and manipulate the stimuli\n", "* Define a trial loop (TrialHandler)\n", "* Draw stimuli onto the screen\n", "* Control the timing\n", "* Register the keyboard input\n", "* Quit the experiment from anywhere" ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "What you need" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PsychoPy is a powerful stimulus generation and experimental control software, meaning that you need equally powerful hardware for it to function properly. Issues often appear when using an old machine, old (integrated) graphics card or when display drivers are not up-to-date. We will now run a quick system check (takes about half a minute) to see where you are (open a web report at the end of it; issues will be indicated in red):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%run ../check_config.py" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "4.4992 \tWARNING \tUser requested fullscreen with size [800 600], but screen is actually [1920, 1200]. Using actual size\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "6.9827 \tWARNING \tCould not raise thread priority with sched_setscheduler.\n", "To enable rush(), if you are using a debian-based linux, try this in a terminal window:\n", " 'sudo setcap cap_sys_nice=eip /usr/bin/python' [NB: You may need to install 'setcap' first.]\n", "If you are using the system's python (eg /usr/bin/python2.x), its highly recommended\n", "to change cap_sys_nice back to normal afterwards:\n", " 'sudo setcap cap_sys_nice= /usr/bin/python'\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "8.2164 \tWARNING \tCould not raise thread priority with sched_setscheduler.\n", "To enable rush(), if you are using a debian-based linux, try this in a terminal window:\n", " 'sudo setcap cap_sys_nice=eip /usr/bin/python' [NB: You may need to install 'setcap' first.]\n", "If you are using the system's python (eg /usr/bin/python2.x), its highly recommended\n", "to change cap_sys_nice back to normal afterwards:\n", " 'sudo setcap cap_sys_nice= /usr/bin/python'\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "13.1987 \tWARNING \tt of last frame was 166.69ms (=1/5)\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "15.2987 \tWARNING \tt of last frame was 33.29ms (=1/30)\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "16.3487 \tWARNING \tt of last frame was 33.29ms (=1/30)\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "17.3988 \tWARNING \tt of last frame was 33.32ms (=1/30)\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "18.4487 \tWARNING \tMultiple dropped frames have occurred - I'll stop bothering you about them!\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "benchmark version: 0.1\n", "full-screen: True\n", "dots_circle: 700\n", "dots_square: 1400\n", "available memory: 1098M\n", "psychopy: 1.80.06\n", "locale: en_US.UTF-8\n", "python version: 2.7.6 (64bit)\n", "wx: 2.8.12.1 (gtk2-unicode)\n", "pyglet: 1.1.4\n", "rush: True\n", "openGL version: 3.0 Mesa 10.1.3\n", "openGL vendor: X.Org\n", "screen size: 1920 x 1200\n", "have shaders: True\n", "visual sync (refresh): 16.67 ms/frame\n", "refresh stability (SD): 2.16 ms\n", "no dropped frames: 0 / 180\n", "pyglet avbin: 7\n", "openGL max vertices: 3000\n", "GL_ARB_multitexture: True\n", "GL_EXT_framebuffer_object: True\n", "GL_ARB_fragment_program: True\n", "GL_ARB_shader_objects: True\n", "GL_ARB_vertex_shader: True\n", "GL_ARB_texture_non_power_of_two: True\n", "GL_ARB_texture_float: True\n", "GL_STEREO: False\n", "pyo: 0.6.8\n", "microphone latency: 0.0058 s\n", "speakers latency: 0.0239 s\n", "flac: (missing)\n", "numpy: 1.8.1\n", "scipy: 0.13.3\n", "matplotlib: 1.3.1\n", "platform: linux 3.13.0-24-generic\n", "internet access: True\n", "auto proxy: True\n", "background processes: Dropbox ...\n", "OpenSSL: 1.0.1f 6 Jan 2014\n", "CPU speed test: 0.008 s\n", "PIL: 1.1.7\n", "openpyxl: 1.7.0\n", "lxml: import ok\n", "setuptools: 3.3\n", "pytest: --\n", "sphinx: 1.2.2\n", "psignifit: --\n", "pyserial: 2.6\n", "pp: 1.6.4\n", "pynetstation: import ok\n", "ioLabs: --\n", "labjack: --\n" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some problems are not important for this course (such as microphone latency) but others (e.g., outdated drivers) will be very visible throughout the course. You may be unable to draw certain stimuli or have them show up incorrectly." ] }, { "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 = {\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", "print exp_info" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice how you can make drop-down lists and tickboxes. As you can see, 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, core\n", "\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 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 any information you like in 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": [ "# DON'T RUN THIS\n", "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 seen 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 from 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=(600,400), 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, there 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 an object for each kind of stimulus, and as you go into experimental loop, their properties are manipulated to show different stimuli on each trial. Note how this is a different strategy from creating one 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 one bitmap object defined upfront, and we will be updating which image is shown by updating the image file path of this bitmap object." ] }, { "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 [ImageStim](http://www.psychopy.org/api/visual/imagestim.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=(600,400), color='white', units='pix')\n", "bitmap = visual.ImageStim(win, 'images/1a.jpg', size=(600,400))\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=(600,400), color='white', units='pix')\n", "bitmap = visual.ImageStim(win, 'images/1a.jpg', size=(600,400))\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=(600,400), color='white', units='pix')\n", "bitmap = visual.ImageStim(win, 'images/1a.jpg', size=(600,400))\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 [circle](http://www.psychopy.org/api/visual/circle.html) which we need to create the bubbles in our experiment:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "win = visual.Window(size=(600,400), 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=(600,400), color='white', units='pix')\n", "text = visual.TextStim(win, text='Press spacebar to start the trial', color='red', height=20)\n", "text.draw()\n", "win.flip()\n", "core.wait(3) # seconds\n", "win.close()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Updating stimulus properties" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "Before each trial we need to be able to update images and their orientations. This is generally done using `set` commands, such as `setImage` and `setOri` in our example:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from psychopy import visual, core\n", "win = visual.Window(size=(600,400), color='white', units='pix')\n", "bitmap = visual.ImageStim(win, 'images/1a.jpg', size=(600,400))\n", "bitmap.draw() # draw bitmap on the window\n", "win.flip() # make bitmap visible\n", "core.wait(3)\n", "\n", "bitmap.setImage('images/2a.jpg')\n", "bitmap.setOri(180)\n", "bitmap.draw()\n", "win.flip()\n", "core.wait(3)\n", "\n", "win.close()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You could, of course, just define a new stimulus (i.e., `bitmap = visual.SimpleImageStim(win, 'images/2a.jpg'`) but this is slower and therefore not recommended in actual experiments." ] }, { "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 trial\", 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=(600,400), color='white', units='pix')\n", "text = visual.TextStim(win, text='Press spacebar to start the trial', color='red', height=20)\n", "text.draw()\n", "win.flip()\n", "\n", "keys = event.waitKeys(keyList=['space', 'escape'])\n", "print keys\n", "\n", "if 'escape' in keys:\n", " win.close()\n", "else:\n", " print 'Start of the trial'\n", " win.close()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Timing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PsychoPy has been built with [precise timing control in mind](http://www.psychopy.org/general/timing/millisecondPrecision.html) (see also [a recent report in PLOS ONE claiming otherwise](http://dx.doi.org/10.1371/journal.pone.0085108) and [PsychoPy's community showing that this is not the case](http://www.plosone.org/article/comments/info%3Adoi%2F10.1371%2Fjournal.pone.0085108)).\n", "\n", "There are two ways to control timing in PsychoPy: clock-based and frame-based." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Clock-based timing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PsychoPy provides a `core.Clock` class for a very easy control of timing. You simply define a clock and use the `reset()` command to reset it to zero and, effectively, start counting. You can check how much time passed by calling the `getTime()` command. So we can rewrite the code above using `core.wait(3)` in the following manner:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from psychopy import visual, core\n", "\n", "win = visual.Window(size=(600,400), color='white', units='pix')\n", "bitmap = visual.ImageStim(win, 'images/1a.jpg', size=(600,400))\n", "\n", "# define and start a clock\n", "rt_clock = core.Clock()\n", "rt_clock.reset()\n", "# this is equivallent to core.wait(3)\n", "while rt_clock.getTime() < 3:\n", " bitmap.draw() # draw bitmap on the window\n", " win.flip() # make bitmap visible\n", " \n", "win.close()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At this point there is little reason to do so because we are not changing anything about the stimulus while it is on the screen. Usually, however, either we are updating something (stimulus position, color etc) or at least recording user input, and this routine becomes important." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Frame-based timing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another way to control timing is to present stimuli for a specified number of frames. Why frames? You should understand that the computer monitor does not update information immediately. Rather, images on the screen are updated at each refresh period. If you have a typical monitor of 60 Hz, that means it is updating every 1000/60 = 16.667 ms. That's just how often you can change something on a screen. The implication is that you **cannot** present a stimulus for 20 or 25 ms, for example. Not good your taste? Get a better monitor, like 100 Hz.\n", "\n", "Because of this, many experiments that need short stimulus durations rely on frame-based timing. The basic implementation is like this:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from psychopy import visual, core\n", "\n", "nframes = 12 # will see stimulus for 12 frames, i.e., 200 ms on most monitors\n", "\n", "win = visual.Window(size=(600,400), color='white', units='pix')\n", "bitmap = visual.ImageStim(win, 'images/1a.jpg', size=(600,400))\n", "\n", "core.wait(2) # blank screen initially\n", "\n", "for frame in range(nframes):\n", " bitmap.draw() # draw bitmap on the window\n", " win.flip() # make bitmap visible\n", "\n", "win.flip()\n", "core.wait(2) # blank screen afterwards\n", " \n", "win.close()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Which one to use?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It depends on your specific experiment. For long stimulus presentation times (hundreds of miliseconds), it's okay to use `core.Clock` because you probably don't need milisecond precision and it's more user-friendly. But for short durations (tens of miliseconds) or when you care about precise timing you should go with frame-based timing.\n", "\n", "Another point to consider is a slightly different behavior of the two. Clock-based timing will always end exactly the specified amount of miliseconds after it started. Frame-based timing, on the other hand, will present the exact number of frames that you asked for. The more frames you ask for, the more likely it is that some will be dropped, meaning that several frames will be drawn not a for a single duration of a frame (e.g., 16.667 ms) but for twice as long. Since the amount of frames is fixed, the total duration of a trial will consequently be longer by that amount. This is unlikely to happen when you have only several frames presented but if you try to present stimulus for 8 sec using frame-based timing, you may get slightly longer stimulus durations. In many cases, that doesn't matter. But in fMRI experiments where stimulus onset must be time-locked to the onset of a scanner sequence, this may lead to an unacceptable desynchronization." ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Running experiment" ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "During the trial" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "During the trial, we 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", "\n", "scrsize = (600,400)\n", "win = visual.Window(size=scrsize, color='white', units='pix')\n", "bitmap1 = visual.ImageStim(win, 'images/1a.jpg', size=scrsize)\n", "bitmap2 = visual.ImageStim(win, 'images/1b.jpg', size=scrsize)\n", "\n", "for i in range(5):\n", " bitmap1.draw() # draw bitmap on the window\n", " win.flip() # make bitmap visible\n", " core.wait(.5)\n", "\n", " bitmap2.draw()\n", " win.flip()\n", " core.wait(.5)\n", " \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", "\n", "scrsize = (600,400)\n", "win = visual.Window(size=scrsize, color='white', units='pix')\n", "bitmap1 = visual.ImageStim(win, 'images/1a.jpg', size=scrsize)\n", "bitmap2 = visual.ImageStim(win, 'images/1b.jpg', size=scrsize)\n", "bitmap = bitmap1\n", "\n", "for i in range(10): # note that we now need 10, not 5 iterations\n", " # change the bitmap\n", " if bitmap == bitmap1:\n", " bitmap = bitmap2\n", " else:\n", " bitmap = bitmap1\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 and response time" ] }, { "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 and timing, remember that we used `event.waitKeys` before. We can try to use it here as well.\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", "\n", "scrsize = (600,400)\n", "win = visual.Window(size=scrsize, color='white', units='pix')\n", "bitmap1 = visual.ImageStim(win, 'images/1a.jpg', size=scrsize)\n", "bitmap2 = visual.ImageStim(win, 'images/1b.jpg', size=scrsize)\n", "bitmap = bitmap1\n", "\n", "# Initialize clock to register response time\n", "rt_clock = core.Clock()\n", "rt_clock.reset() # set rt clock to 0\n", "done = False\n", "\n", "# Empty the keypresses list\n", "keys = None\n", "\n", "# Start the trial\n", "# Stop trial if spacebar or escape has been pressed, or if 30s have passed\n", "while keys is None and rt_clock.getTime() < 30:\n", " \n", " # Switch the image\n", " if bitmap == bitmap1:\n", " bitmap = bitmap2\n", " else:\n", " bitmap = bitmap1\n", " bitmap.draw()\n", " \n", " # Show the new screen we've drawn\n", " win.flip()\n", " \n", " # For 0.5s, listen for a spacebar or escape press\n", " keys = event.waitKeys(maxWait=.5, keyList=['space', 'escape'], timeStamped=rt_clock)\n", " print keys\n", " \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": [ "from psychopy import visual, core, event\n", "\n", "scrsize = (600,400)\n", "win = visual.Window(size=scrsize, color='white', units='pix')\n", "bitmap1 = visual.ImageStim(win, image='images/1a.jpg', size=scrsize)\n", "bitmap2 = visual.ImageStim(win, image='images/1b.jpg', size=scrsize)\n", "bitmap = bitmap1\n", "\n", "# Initialize clock to register response time\n", "rt_clock = core.Clock()\n", "rt_clock.reset() # set rt clock to 0\n", "\n", "# Initialize clock to control stimulus presentation time\n", "change_clock = core.Clock()\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 len(keys) == 0 and rt_clock.getTime() < 30:\n", " \n", " # Switch the image\n", " if bitmap == bitmap1:\n", " bitmap = bitmap2\n", " else:\n", " bitmap = bitmap1\n", " bitmap.draw()\n", " \n", " # Show the new screen we've drawn\n", " win.flip()\n", " \n", " # For 0.5s, listen for a spacebar or escape press\n", " change_clock.reset()\n", " while change_clock.getTime() <=.5:\n", " keys = event.getKeys(keyList=['space', 'escape'])\n", " print keys\n", " if len(keys) > 0:\n", " rt = rt_clock.getTime()\n", " break \n", " \n", "print keys, rt\n", "win.close()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This approach 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. So 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 of increasing radius at random positions \n", "for radius in range(n_bubbles):\n", " bubble.setRadius(radius/2.)\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` list shouldn't be empty). If not, that means the time limit was reached." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Analyze the keypress\n", "if keys:\n", " if 'escape' in keys:\n", " # Escape press = quit the experiment\n", " break\n", " else:\n", " # Spacebar press = correct change detection; register response time\n", " acc = 1\n", " rt = rt_clock.getTime() \n", "else:\n", " # No press = failed change detection; maximal response time\n", " acc = 0\n", " rt = timelimit" ], "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", "\n", " # Set the images, set the orientation\n", " im_fname = os.path.join(impath, trial['im'])\n", " bitmap1.setImage(im_fname + asfx)\n", " bitmap1.setFlipHoriz(trial['ori'])\n", " bitmap2.setImage(im_fname + bsfx)\n", " bitmap2.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": [ "%load script_final.py" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 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 = (600,400) # 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", "# Get subject name, gender, age, handedness through a dialog box\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", "# Get date and time\n", "exp_info['date'] = data.getDateStr()\n", "exp_info['exp_name'] = exp_name\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 condition 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 (rotated by 180 deg)\n", "orilist = [0,180]*(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", "win = visual.Window(size=scrsize, color='white', units='pix', fullscr=False)\n", "\n", "# Define trial start text\n", "start_message = visual.TextStim(win,\n", " text=\"Press spacebar to start the trial. Hit spacebar again when you detect a change.\",\n", " color='red', height=20)\n", "\n", "# Define bitmap stimulus (contents can still change)\n", "bitmap1 = visual.ImageStim(win, size=scrsize)\n", "bitmap2 = visual.ImageStim(win, size=scrsize)\n", "\n", "# Define a bubble (position and size can still change)\n", "bubble = visual.Circle(win, fillColor='black', lineColor='black')\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", "# Initialize two clocks:\n", "# - for image change time\n", "# - for response time\n", "change_clock = core.Clock()\n", "rt_clock = core.Clock()\n", "\n", "# Run through the trials\n", "for trial in trials:\n", "\n", " # Display trial start text\n", " start_message.draw()\n", " win.flip()\n", "\n", " # Wait for a spacebar press to start the trial, or escape to quit\n", " keys = event.waitKeys(keyList=['space', 'escape'])\n", "\n", " # Set the images, set the orientation\n", " im_fname = os.path.join(impath, trial['im'])\n", " bitmap1.setImage(im_fname + asfx)\n", " bitmap1.setOri(trial['ori'])\n", " bitmap2.setImage(im_fname + bsfx)\n", " bitmap2.setOri(trial['ori'])\n", " bitmap = bitmap1\n", "\n", " # Set the clocks to 0\n", " change_clock.reset()\n", " rt_clock.reset()\n", "\n", " # Empty the keypresses list\n", " # Leave an 'escape' press in for immediate exit\n", " if 'space' in keys:\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 keys and rt_clock.getTime() < timelimit:\n", "\n", " # Switch the image\n", " if bitmap == bitmap1:\n", " bitmap = bitmap2\n", " else:\n", " bitmap = bitmap1\n", "\n", " bitmap.draw()\n", "\n", " # Draw bubbles of increasing radius at random positions\n", " for radius in range(n_bubbles):\n", " bubble.setRadius(radius/2.)\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 the duration of 'changetime',\n", " # Listen for a spacebar or escape press\n", " change_clock.reset()\n", " while change_clock.getTime() <= changetime:\n", " keys = event.getKeys(keyList=['space','escape'])\n", " if keys:\n", " break\n", "\n", " # Analyze the keypress\n", " if keys:\n", " if 'escape' in keys:\n", " # Escape press = quit the experiment\n", " break\n", " else:\n", " # Spacebar press = correct change detection; register response time\n", " acc = 1\n", " rt = rt_clock.getTime()\n", "\n", " else:\n", " # No press = failed change detection; maximal response time\n", " acc = 0\n", " rt = timelimit\n", "\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\n", "win.close()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [ { "ename": "SystemExit", "evalue": "0", "output_type": "pyerr", "traceback": [ "An exception has occurred, use %tb to see the full traceback.\n", "\u001b[1;31mSystemExit\u001b[0m\u001b[1;31m:\u001b[0m 0\n" ] }, { "output_type": "stream", "stream": "stderr", "text": [ "To exit: use 'exit', 'quit', or Ctrl-D.\n" ] } ], "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 = (600,400) # 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", "# Get subject name, gender, age, handedness through a dialog box\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", "# Get date and time\n", "exp_info['date'] = data.getDateStr()\n", "exp_info['exp_name'] = exp_name\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 condition 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 (rotated by 180 deg)\n", "orilist = [0,180]*(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", "win = visual.Window(size=scrsize, color='white', units='pix', fullscr=False)\n", "\n", "# Define trial start text\n", "start_message = visual.TextStim(win,\n", " text=\"Press spacebar to start the trial. Hit spacebar again when you detect a change.\",\n", " color='red', height=20)\n", "\n", "# Define bitmap stimulus (contents can still change)\n", "bitmap1 = visual.ImageStim(win, size=scrsize)\n", "bitmap2 = visual.ImageStim(win, size=scrsize)\n", "\n", "# Define a bubble (position and size can still change)\n", "bubble = visual.Circle(win, fillColor='black', lineColor='black')\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", "# Initialize two clocks:\n", "# - for image change time\n", "# - for response time\n", "change_clock = core.Clock()\n", "rt_clock = core.Clock()\n", "\n", "# Run through the trials\n", "for trial in trials:\n", "\n", " # Display trial start text\n", " start_message.draw()\n", " win.flip()\n", "\n", " # Wait for a spacebar press to start the trial, or escape to quit\n", " keys = event.waitKeys(keyList=['space', 'escape'])\n", "\n", " # Set the images, set the orientation\n", " im_fname = os.path.join(impath, trial['im'])\n", " bitmap1.setImage(im_fname + asfx)\n", " bitmap1.setOri(trial['ori'])\n", " bitmap2.setImage(im_fname + bsfx)\n", " bitmap2.setOri(trial['ori'])\n", " bitmap = bitmap1\n", "\n", " # Set the clocks to 0\n", " change_clock.reset()\n", " rt_clock.reset()\n", "\n", " # Empty the keypresses list\n", " # Leave an 'escape' press in for immediate exit\n", " if 'space' in keys:\n", " keys = []\n", "\n", " # Start the trial\n", " # Stop trial if spacebar or escape has been pressed, or if 30s have passed\n", " while len(keys) == 0 and rt_clock.getTime() < timelimit:\n", "\n", " # Switch the image\n", " if bitmap == bitmap1:\n", " bitmap = bitmap2\n", " else:\n", " bitmap = bitmap1\n", "\n", " bitmap.draw()\n", "\n", " # Draw bubbles of increasing radius at random positions\n", " for radius in range(n_bubbles):\n", " bubble.setRadius(radius/2.)\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 the duration of 'changetime',\n", " # Listen for a spacebar or escape press\n", " change_clock.reset()\n", " while change_clock.getTime() <= changetime:\n", " keys = event.getKeys(keyList=['space','escape'])\n", " if len(keys) > 0:\n", " break\n", "\n", " # Analyze the keypress\n", " if keys:\n", " if 'escape' in keys:\n", " # Escape press = quit the experiment\n", " break\n", " else:\n", " # Spacebar press = correct change detection; register response time\n", " acc = 1\n", " rt = rt_clock.getTime()\n", "\n", " else:\n", " # No press = failed change detection; maximal response time\n", " acc = 0\n", " rt = timelimit\n", "\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\n", "win.close()\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Peaking at data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you ran the entire experiment, you can find the data in the data folder. It is in a 'csv' (comma-separated value) file and you may not know what to do with it.\n", "\n", "An easy thing to do is to open it with Excel and use Data > Split Text into Columns.\n", "\n", "But if you want to analyze data in Python, you can too! We'll talk more about that in [Part 5](../Part5/Part5_psychopy_ext) (or [read more on our wiki](http://gestaltrevision.be/wiki/analysis/pythonanalysis)) but here's a preview how to read in your data using `pandas`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import glob\n", "import pandas\n", "\n", "datafile = glob.glob('data/*.csv')[0]\n", "print datafile\n", "df = pandas.read_csv(datafile)\n", "df" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## " ] } ], "metadata": {} } ] }