<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7277754001418716339</id><updated>2011-09-03T17:31:42.313-07:00</updated><category term='stimuli'/><category term='troubleshooting'/><category term='discussion'/><category term='object-oriented'/><category term='blogger'/><category term='response'/><category term='tutorials'/><category term='documentation'/><category term='quitting'/><category term='python'/><category term='books'/><category term='mac'/><category term='lists'/><category term='keyboard'/><category term='indentation'/><category term='freetext'/><category term='ButtonChooser'/><category term='timing'/><title type='text'>The collaborative Pyepl blog</title><subtitle type='html'>PyEPL  (the Python Experiment-Programming Library) - tutorials, snippets and discussion</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>14</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-6429274956004530764</id><published>2011-08-23T12:48:00.000-07:00</published><updated>2011-08-23T12:56:37.528-07:00</updated><title type='text'>Interactive PyEPL session</title><content type='html'>Sometimes, I am interested in exploring some PyEPL feature without writing a whole experiment with setting up subject and all that. I rather like to have an interactive python shell and hack away. Here is the code on how to do that:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;div&gt;from pyepl.locals import *&lt;br /&gt;import os&lt;br /&gt;import shutil&lt;br /&gt;&lt;br /&gt;# create an experiment object:&lt;br /&gt;# parse command-line arguments&lt;br /&gt;# &amp;amp; initialize pyepl subsystems&lt;br /&gt;tdir = "/tmp/iPyEPL"&lt;br /&gt;subj = "isubj"&lt;br /&gt;subjdir = os.path.join(tdir,subj)&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;if not os.path.exists(tdir):&lt;br /&gt;  os.makedirs(tdir)&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;if os.path.exists(subjdir):&lt;br /&gt;  shutil.rmtree(subjdir)&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;exp = Experiment(archive=tdir,subject=subj,fullscreen=False)&lt;br /&gt;exp.setBreak()&lt;br /&gt;&lt;br /&gt;# load some tracks&lt;br /&gt;vt = VideoTrack("video")&lt;br /&gt;kt = KeyTrack("key")&lt;br /&gt;#at = AudioTrack("audio")&lt;br /&gt;&lt;br /&gt;# reset the display to black&lt;br /&gt;vt.clear("black")&lt;br /&gt;vt.updateScreen()&lt;br /&gt;&lt;br /&gt;# create a PresentationClock object&lt;br /&gt;# for timing&lt;br /&gt;clk = PresentationClock()&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Just copy and paste into your python session and you are ready to explore. Thanks to &lt;a href="http://faculty.psy.ohio-state.edu/sederberg/"&gt;Per&lt;/a&gt; for that!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-6429274956004530764?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/6429274956004530764/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=6429274956004530764' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/6429274956004530764'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/6429274956004530764'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2011/08/interactive-pyepl-session.html' title='Interactive PyEPL session'/><author><name>Andrej</name><uri>http://www.blogger.com/profile/05029517703322301601</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-1956241183511404236</id><published>2011-08-08T11:34:00.000-07:00</published><updated>2011-08-08T11:42:58.050-07:00</updated><title type='text'>How to attach a function to a class</title><content type='html'>In &lt;a href="http://pyepl.blogspot.com/2009/10/presenting-stimulus-for-fixed-duration.html"&gt;"Presenting stimulus for fixed duration"&lt;/a&gt; we saw how we can easily write a new class to extend existing classes with new functions. While it works, I always had the problem to put this class on every computer and to have it work correctly with different Python Path settings. So, here is a neat trick how to do the same without writing your own class:&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from pyepl.locals import *&lt;br /&gt;def present_fixed_dur(clk=None, duration=None, bc=None, minDuration=None):&lt;br /&gt;        """&lt;br /&gt;        This is just like the standard Image.present, except&lt;br /&gt;        that if you press a button, the image stays on the&lt;br /&gt;        screen until the full duration has passed.&lt;br /&gt;&lt;br /&gt;        I got rid of the jitter argument, because it makes&lt;br /&gt;        things complicated.&lt;br /&gt;        """&lt;br /&gt;&lt;br /&gt;        v = VideoTrack.lastInstance()&lt;br /&gt;        &lt;br /&gt;        # get the clock if needed&lt;br /&gt;        if clk is None: clk = PresentationClock()&lt;br /&gt;&lt;br /&gt;        # show the image&lt;br /&gt;        t = v.showCentered(self)&lt;br /&gt;        timestamp = v.updateScreen(clk)&lt;br /&gt;&lt;br /&gt;        if bc:&lt;br /&gt;            # wait for button press&lt;br /&gt;            button,bc_time = bc.waitWithTime(minDuration,duration,clk)&lt;br /&gt;            # figure out how much time is remaining, now&lt;br /&gt;            # that they've pressed the button, and delay for&lt;br /&gt;            # just that&lt;br /&gt;            rt = bc_time[0] - timestamp[0]&lt;br /&gt;            clk.delay(duration-rt)&lt;br /&gt;        else:&lt;br /&gt;            clk.delay(duration)&lt;br /&gt;&lt;br /&gt;        # unshow that image&lt;br /&gt;        v.unshow(t)&lt;br /&gt;        upd_ts = v.updateScreen(clk)&lt;br /&gt;        # print 'presented for %ims' % (upd_ts[0] - timestamp[0])&lt;br /&gt;&lt;br /&gt;        if bc: return timestamp,button,bc_time&lt;br /&gt;        else: return timestamp&lt;br /&gt;&lt;br /&gt;# Use a reference to the function and attach it as a attribute to the Image class&lt;br /&gt;Image.present_fixed_dur = present_fixed_dur&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Et voila, we extended the Image class on the fly. :)&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-1956241183511404236?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/1956241183511404236/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=1956241183511404236' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/1956241183511404236'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/1956241183511404236'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2011/08/how-to-attach-function-to-class.html' title='How to attach a function to a class'/><author><name>Andrej</name><uri>http://www.blogger.com/profile/05029517703322301601</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-21034103523129946</id><published>2009-10-22T08:35:00.000-07:00</published><updated>2009-10-22T08:36:59.751-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='response'/><category scheme='http://www.blogger.com/atom/ns#' term='ButtonChooser'/><category scheme='http://www.blogger.com/atom/ns#' term='timing'/><category scheme='http://www.blogger.com/atom/ns#' term='stimuli'/><title type='text'>Presenting a stimulus for a fixed duration (despite button presses)</title><content type='html'>Especially with fMRI, I want my trials to last a particular duration, whether or not the subject responded by pressing a button.&lt;br /&gt;&lt;br /&gt;The standard PRESENT function terminates as soon as a button has been pressed. You could immediately re-present the stimulus for the remaining duration, but this creates an annoying flicker.&lt;br /&gt;&lt;br /&gt;I sub-classed Text and Image to add a PRESENT_FIXED_DUR function that deals with this problem. This issue was also raised on the &lt;a href="http://sourceforge.net/projects/pyepl/forums/forum/548620/topic/3437434"&gt;PyEPL mailing list&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;For images:&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;# from pyepl.hardware.graphics import Image&lt;br /&gt;from pyepl.locals import Image&lt;br /&gt;from pyepl.display import VideoTrack&lt;br /&gt;from pyepl.locals import PresentationClock&lt;br /&gt;&lt;br /&gt;class Image2(Image):&lt;br /&gt;&lt;br /&gt;    def present_fixed_dur(self, clk=None, duration=None, bc=None, minDuration=None):&lt;br /&gt;        """&lt;br /&gt;        This is just like the standard Image.present, except&lt;br /&gt;        that if you press a button, the image stays on the&lt;br /&gt;        screen until the full duration has passed.&lt;br /&gt;&lt;br /&gt;        I got rid of the jitter argument, because it makes&lt;br /&gt;        things complicated.&lt;br /&gt;        """&lt;br /&gt;&lt;br /&gt;        v = VideoTrack.lastInstance()&lt;br /&gt;        &lt;br /&gt;        # get the clock if needed&lt;br /&gt;        if clk is None: clk = PresentationClock()&lt;br /&gt;&lt;br /&gt;        # show the image&lt;br /&gt;        t = v.showCentered(self)&lt;br /&gt;        timestamp = v.updateScreen(clk)&lt;br /&gt;&lt;br /&gt;        if bc:&lt;br /&gt;            # wait for button press&lt;br /&gt;            button,bc_time = bc.waitWithTime(minDuration,duration,clk)&lt;br /&gt;            # figure out how much time is remaining, now&lt;br /&gt;            # that they've pressed the button, and delay for&lt;br /&gt;            # just that&lt;br /&gt;            rt = bc_time[0] - timestamp[0]&lt;br /&gt;            clk.delay(duration-rt)&lt;br /&gt;        else:&lt;br /&gt;            clk.delay(duration)&lt;br /&gt;&lt;br /&gt;        # unshow that image&lt;br /&gt;        v.unshow(t)&lt;br /&gt;        upd_ts = v.updateScreen(clk)&lt;br /&gt;        # print 'presented for %ims' % (upd_ts[0] - timestamp[0])&lt;br /&gt;&lt;br /&gt;        if bc: return timestamp,button,bc_time&lt;br /&gt;        else: return timestamp&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;For text:&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;# from pyepl.hardware.graphics import Image&lt;br /&gt;from pyepl.locals import Text&lt;br /&gt;from pyepl.display import VideoTrack&lt;br /&gt;&lt;br /&gt;class Text2(Text):&lt;br /&gt;&lt;br /&gt;    def present_fixed_dur(self, clk=None, duration=None, bc=None, minDuration=None, xProp=.5, yProp=.5):&lt;br /&gt;        """&lt;br /&gt;        Just as Text2.present, but (like&lt;br /&gt;        Image2.present_fixed_dur): the standard&lt;br /&gt;        Text.present, except that if you press a button, the&lt;br /&gt;        stimulus stays on the screen until the full duration&lt;br /&gt;        has passed.&lt;br /&gt;        """&lt;br /&gt;&lt;br /&gt;        # print 'starting present_fixed_dur'&lt;br /&gt;        v = VideoTrack.lastInstance()&lt;br /&gt;        &lt;br /&gt;        # get the clock if needed&lt;br /&gt;        if clk is None: clk = exputils.PresentationClock()&lt;br /&gt;&lt;br /&gt;        # show the image&lt;br /&gt;        t = v.showProportional(self,xProp,yProp)&lt;br /&gt;        timestamp = v.updateScreen(clk)&lt;br /&gt;&lt;br /&gt;        if bc:&lt;br /&gt;            # wait for button press&lt;br /&gt;            button,bc_time = bc.waitWithTime(minDuration,duration,clk)&lt;br /&gt;            rt = bc_time[0] - timestamp[0]&lt;br /&gt;            # print 'delaying by %i' % (duration-rt)&lt;br /&gt;            # only delay if duration is True&lt;br /&gt;            if duration and (duration-rt): clk.delay(duration-rt)&lt;br /&gt;        else:&lt;br /&gt;            # print 'delaying by %i' % duration&lt;br /&gt;            if duration: clk.delay(duration)&lt;br /&gt;&lt;br /&gt;        v.unshow(t)&lt;br /&gt;        v.updateScreen(clk)&lt;br /&gt;&lt;br /&gt;        # print 'ending present_fixed_dur'&lt;br /&gt;&lt;br /&gt;        if bc: return timestamp,button,bc_time&lt;br /&gt;        else: return timestamp&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-21034103523129946?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/21034103523129946/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=21034103523129946' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/21034103523129946'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/21034103523129946'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2009/10/presenting-stimulus-for-fixed-duration.html' title='Presenting a stimulus for a fixed duration (despite button presses)'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-8387936909556712609</id><published>2008-08-08T14:51:00.000-07:00</published><updated>2008-08-08T14:58:54.307-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tutorials'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><title type='text'>Getting up to speed with Python</title><content type='html'>PyEPL is my favorite way to write experiments, and I'd recommend it strongly to anyone who likes programming. However, if you don't like programming, or you really hate the idea of learning to program, you're probably better off with E-Prime or something akin.&lt;br /&gt;&lt;br /&gt;If you're new to the idea of programming but enthusiastic about it, then I'd strongly recommend going through the first 8 chapters of &lt;a href="http://www.amazon.com/Python-Programming-Absolute-Beginner-Second/dp/1598631128"&gt;Michael Dawson's Python Programming for the Absolute Beginner (2nd edn)&lt;/a&gt;. He covers a wide range of essential python syntax and functionality, with lots and lots of examples. Even if you're not that excited about video games (which comprise the majority of his examples), it's still an excellent and reasonably comprehensive resource. Going through that before trying to write your first experiment will make things proceed much more smoothly.&lt;br /&gt;&lt;br /&gt;If you're an experienced programmer who's new to Python (or just want to brush up further), then I'd strongly recommend &lt;a href="http://diveintopython.org/index.html"&gt;Mark Pilgrim's Dive into Python&lt;/a&gt;. This is actually completely freely available online, but the book edition is pretty affordable and sends a message of yayness to the author and publisher.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-8387936909556712609?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/8387936909556712609/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=8387936909556712609' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/8387936909556712609'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/8387936909556712609'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/getting-up-to-speed-with-python.html' title='Getting up to speed with Python'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-7758076558499235563</id><published>2008-08-08T14:34:00.000-07:00</published><updated>2008-08-08T15:26:21.360-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='object-oriented'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorials'/><title type='text'>A skeleton Experiment class</title><content type='html'>I've experimented with lots of different ways of organizing my PyEPL experiment code. I like to place different parts of the experiment in different functions - this makes it easier to figure out what's going on where, and helps greatly when you want to reuse pieces in future experiments. Because most experiments require you to keep track of lots of variables, I'd often end up feeding many, many parameters into each of my functions, e.g.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;############################################################&lt;br /&gt;def individStimulus(sessionlog, &lt;br /&gt;                 video, audio, backgrounds, clips,&lt;br /&gt;                 clock, bc, wp, state, &lt;br /&gt;                 config, study_run_no, wordno, taskno, prevTask):&lt;br /&gt;    ...&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Sadly, that is a real example.&lt;br /&gt;&lt;br /&gt;In order to avoid having to pass the same horde of variables into each function (VideoTrack, PresentationClock etc.), I tried creating a bunch of global variables that would be shared throughout my experiment code. I'd initialize them all at the very top, but then every function that used any of them would have to declare those variables as global, e.g.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;############################################################&lt;br /&gt;def prepare():&lt;br /&gt;    """&lt;br /&gt;    Prepare the sequences.&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;    global exp&lt;br /&gt;    global config&lt;br /&gt;    global video&lt;br /&gt;    global audio&lt;br /&gt;    global keyboard&lt;br /&gt;    global sessionlog&lt;br /&gt;    global cueslog&lt;br /&gt;    global clock&lt;br /&gt;    global study_seqs&lt;br /&gt;    global loci_pool&lt;br /&gt;    global items_pool&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This was really no better. In fact, there's a potential gotcha awaiting the unwary when using global variables inside a function if you forget to declare the variables as global, since Python will simply assume that these are new local variables of the same name (see &lt;a href="http://iwiwdsmi.blogspot.com/2008/01/python-variable-scope-notes.html"&gt;Example 2&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;Now, older and wiser, I have realized that the one true way of organizing your experiment code is to subclass the PyEPL Experiment class, and make use of member variables. Describing classes is beyond the scope of this document, but &lt;a href="http://pyepl.blogspot.com/2008/08/getting-up-to-speed-with-python.html"&gt;see here for more info on learning Python&lt;/a&gt; if you're unfamiliar with object-oriented programming.&lt;br /&gt;&lt;br /&gt;Without further ado, here's a skeleton Experiment subclass that you can build on when first starting a new experiment:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;my_exp.py&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;from pyepl.locals import *&lt;br /&gt;import shutil&lt;br /&gt;&lt;br /&gt;# only add this if there really is a good reason to exclude&lt;br /&gt;# earlier versions. i like 1.0.29 because it adds&lt;br /&gt;# functionality for resetting accumulated timing error&lt;br /&gt;MIN_PYEPL_VERSION = '1.0.29'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;# TODO: change the classname to something more appropriate&lt;br /&gt;# for your experiment&lt;br /&gt;class MyExperiment(Experiment):&lt;br /&gt;&lt;br /&gt;    ############################################################&lt;br /&gt;    def __init__(self,**kwargs):&lt;br /&gt;        """&lt;br /&gt;        Create the Experiment superclass, set basic&lt;br /&gt;        Experiment stuff up, get the config, and prepare all&lt;br /&gt;        our main Experiment member variables.&lt;br /&gt;        """&lt;br /&gt;&lt;br /&gt;        # this is all standard&lt;br /&gt;        #&lt;br /&gt;        # initialize the Experiment superclass&lt;br /&gt;        Experiment.__init__(self,**kwargs)&lt;br /&gt;&lt;br /&gt;        # allow users to break out of the experiment with&lt;br /&gt;        # escape-F1 (the default key combo)&lt;br /&gt;        self.setBreak()    &lt;br /&gt;        # get config&lt;br /&gt;        self.cfg = self.getConfig()&lt;br /&gt;&lt;br /&gt;        # if you have any randomization, you'll probably&lt;br /&gt;        # want to make sure it's different very time. i&lt;br /&gt;        # think python automatically resets the seed each&lt;br /&gt;        # time, but i keep this in just in case&lt;br /&gt;        random.seed()&lt;br /&gt;&lt;br /&gt;        # set up the logs&lt;br /&gt;        self.vid  = VideoTrack("video")&lt;br /&gt;        self.key  = KeyTrack("keyboard")&lt;br /&gt;        # this is the one i use for storing all my main&lt;br /&gt;        # experiment data&lt;br /&gt;        self.slog = LogTrack("session")&lt;br /&gt;  &lt;br /&gt;        # set up the presentation clock&lt;br /&gt;        try:&lt;br /&gt;            # requires 1.0.29, auto-adjusts timing if there's a lag&lt;br /&gt;            self.clk = PresentationClock(correctAccumulatedErrors=True)&lt;br /&gt;        except:&lt;br /&gt;            # if you don't have 1.0.29 loaded, then just&lt;br /&gt;            # fall back (since the timing will probably be&lt;br /&gt;            # fine anyway)&lt;br /&gt;            self.clk = PresentationClock()&lt;br /&gt;&lt;br /&gt;        self.instr = open(self.cfg.instr_fname,'r').read()&lt;br /&gt;&lt;br /&gt;        # copy the instructions text into the data directory&lt;br /&gt;        # for this session, so that you can refer to it&lt;br /&gt;        # later if you need to&lt;br /&gt;        shutil.copy(self.cfg.instr_fname, self.session.fullPath())&lt;br /&gt;&lt;br /&gt;        self._resetVideo()&lt;br /&gt;&lt;br /&gt;        # TODO call any stimulus generation or other &lt;br /&gt;        # preparation functions that you need to get things ready &lt;br /&gt;        # for the experiment to run.&lt;br /&gt;        self._prepare()&lt;br /&gt;&lt;br /&gt;        self._sanity_check()&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    ############################################################&lt;br /&gt;    def _prepare(self):&lt;br /&gt;        """&lt;br /&gt;        Set everything up before we try and run.&lt;br /&gt;        """&lt;br /&gt;&lt;br /&gt;        # we'll make use of this later&lt;br /&gt;        self.greeting_msg = 'Hello'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    ############################################################&lt;br /&gt;    def _resetVideo(self):&lt;br /&gt;        """&lt;br /&gt;        And then there was no light.&lt;br /&gt;        """&lt;br /&gt;    &lt;br /&gt;        self.vid.clear("black")&lt;br /&gt;        self.vid.updateScreen(self.clk)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    ############################################################&lt;br /&gt;    def _sanity_check(self):&lt;br /&gt;&lt;br /&gt;        """&lt;br /&gt;        This is where i confirm that everything has been set&lt;br /&gt;        up correctly and that everything we'll need for the&lt;br /&gt;        rest of the experiment is in place (so that any&lt;br /&gt;        problems cause a crash now rather than halfway&lt;br /&gt;        through an experiment).&lt;br /&gt;&lt;br /&gt;        For instance, it's a good place to make sure any&lt;br /&gt;        basic constraints are adhered to in your config.&lt;br /&gt;        """&lt;br /&gt;        &lt;br /&gt;        # TODO - add your sanity checks here&lt;br /&gt;        assert 2 + 2 == 4&lt;br /&gt;&lt;br /&gt;    &lt;br /&gt;    ############################################################&lt;br /&gt;    def go(self):&lt;br /&gt;        """&lt;br /&gt;        Run the entire loci1h experiment&lt;br /&gt;        """&lt;br /&gt;    &lt;br /&gt;        # log start of experiment&lt;br /&gt;        self.slog.logMessage('SESS_START')&lt;br /&gt;&lt;br /&gt;        # tell the subject what's about to happen        &lt;br /&gt;        instruct(self.instr)&lt;br /&gt;&lt;br /&gt;        # TODO this is where your experiment code kicks off&lt;br /&gt;        flashStimulus(Text(self.greeting_msg),duration=3000)&lt;br /&gt;&lt;br /&gt;        self.slog.logMessage('SESS_END')&lt;br /&gt;    &lt;br /&gt;        Text("Thank you!\nYou have completed the session.").present(&lt;br /&gt;            clk=self.clk,duration=5000)&lt;br /&gt;        &lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;############################################################&lt;br /&gt;def main_interactive(config='config.py'):&lt;br /&gt;            &lt;br /&gt;    """&lt;br /&gt;    If you want to run things interactively from ipython,&lt;br /&gt;    call this. It's basically the same as the main()&lt;br /&gt;    function.&lt;br /&gt;&lt;br /&gt;    from my_exp import *; exp = main_interactive()&lt;br /&gt;    &lt;br /&gt;    OR if you want to load in a special config file:&lt;br /&gt;&lt;br /&gt;    from my_exp import *; exp = main_interactive('debug_config.py')&lt;br /&gt;    """&lt;br /&gt;    &lt;br /&gt;    exp = MyExperiment(subject='subj00',&lt;br /&gt;                 fullscreen=False,&lt;br /&gt;                 resolution=(640,480),&lt;br /&gt;                 config=config)&lt;br /&gt;    &lt;br /&gt;    exp.go()&lt;br /&gt;    &lt;br /&gt;    return exp&lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;############################################################&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    # make sure we have the min pyepl version&lt;br /&gt;    checkVersion(MIN_PYEPL_VERSION)&lt;br /&gt;    &lt;br /&gt;    # start PyEPL, parse command line options, and do&lt;br /&gt;    # subject housekeeping&lt;br /&gt;    exp = MyExperiment()&lt;br /&gt;&lt;br /&gt;    # now run the subject&lt;br /&gt;    exp.go()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Notice for instance how the _prepare() function creates a member variable (self.greeting_msg). That gets stored in the MyExperiment object, and it's ready to be deployed in go() by the flashStimulus call. No passing around of arguments, no global variables, and it's easy to subclass this MyExperiment class as MySlightlyImprovedExperiment, just overriding the functions that differ.&lt;br /&gt;&lt;br /&gt;I've marked places where you'll almost certainly want to modify and add code for your experiment with a 'TODO' comment. Save the above as my_exp.py.&lt;br /&gt;&lt;br /&gt;You'll also need a config.py:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;config.py&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;instr_fname = 'instr.txt'&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and some instructions:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;instr.txt&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;These are the instructions. You're about to be greeted by&lt;br /&gt;the program.&lt;br /&gt;&lt;br /&gt;Press UP/DOWN or PgUP/PgDOWN to scroll these&lt;br /&gt;instructions. When you get to the bottom of the page, please&lt;br /&gt;discuss what you've read with the experimenter to be sure&lt;br /&gt;that everything is clear, and then press RETURN to move on.&lt;br /&gt;&lt;br /&gt;--- please discuss what you have read with the experimenter ---&lt;br /&gt;&lt;br /&gt;--- then press RETURN to continue ---&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Put all of them in a directory, and you should be able to run them with:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;python my_exp.py -s subj00 --resolution 640x480 --no-eeg --no-fs&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;or fire up python and type:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;    from my_exp import *; exp = main_interactive()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-7758076558499235563?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/7758076558499235563/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=7758076558499235563' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/7758076558499235563'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/7758076558499235563'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/skeleton-experiment-class.html' title='A skeleton Experiment class'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-7313861104324453785</id><published>2008-08-03T20:14:00.000-07:00</published><updated>2008-08-03T20:24:21.860-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='troubleshooting'/><category scheme='http://www.blogger.com/atom/ns#' term='keyboard'/><category scheme='http://www.blogger.com/atom/ns#' term='ButtonChooser'/><title type='text'>Don't initialize the ButtonChooser with lowercase letters</title><content type='html'>Otherwise, you could get an inscrutable ValueError, 'Key already bound' error message. Always use uppercase letters, and all should be well. I think this has something to do with the error-checking in the KeyboardTrack that won't allow multiple ButtonChoosers to simultaneously be checking the same key.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-7313861104324453785?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/7313861104324453785/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=7313861104324453785' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/7313861104324453785'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/7313861104324453785'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/dont-use-initialize-buttonchooser-with.html' title='Don&apos;t initialize the ButtonChooser with lowercase letters'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-3729024909463835726</id><published>2008-08-03T20:04:00.000-07:00</published><updated>2008-08-03T21:14:42.953-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='timing'/><title type='text'>Timing how long things are taking</title><content type='html'>When I first started writing experiments in PyEPL, I had a lot of trouble getting the timing right. I wanted a way to time how long things were taking that didn't make use of the PyEPL PresentationClock, to confirm that all was well. The Stopwatch class below is based heavily on &lt;a href="http://effbot.org/librarybook/timing.htm"&gt;timing-example-2.py&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;"""&lt;br /&gt;This is my wrapper for the time module. There's probably an&lt;br /&gt;easier way to time the duration of things, but when I looked&lt;br /&gt;into timing stuff, this was the best I could come up with...&lt;br /&gt;&lt;br /&gt;To use:&lt;br /&gt;&lt;br /&gt;    t = Stopwatch()&lt;br /&gt;    t.start_time()&lt;br /&gt;    &lt;br /&gt;    # do something&lt;br /&gt;&lt;br /&gt;    elapsed = t.finish()&lt;br /&gt;"""&lt;br /&gt;&lt;br /&gt;import time&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class Stopwatch:&lt;br /&gt;    """&lt;br /&gt;    Creates stopwatch timer objects.&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;    # stores the time the stopwatch was started&lt;br /&gt;    t0 = None&lt;br /&gt;&lt;br /&gt;    # stores the time the stopwatch was last looked at&lt;br /&gt;    t1 = None&lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;    def __init__(self):&lt;br /&gt;        self.t0 = 0&lt;br /&gt;        self.t1 = 0&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    def start(self):&lt;br /&gt;        """&lt;br /&gt;        Stores the current time in t0.&lt;br /&gt;        """&lt;br /&gt;        &lt;br /&gt;        self.t0 = time.time()&lt;br /&gt;        &lt;br /&gt;&lt;br /&gt;    def finish(self):&lt;br /&gt;        """&lt;br /&gt;        Returns the elapsed duration in milliseconds. This&lt;br /&gt;        stores the current time in t1, and calculates the&lt;br /&gt;        difference between t0 (the stored start time) and&lt;br /&gt;        t1, so if you call this multiple times, you'll get a&lt;br /&gt;        larger answer each time.&lt;br /&gt;&lt;br /&gt;        You have to call this in order to update t1.&lt;br /&gt;        """&lt;br /&gt;        &lt;br /&gt;        self.t1 = time.time()&lt;br /&gt;        return self.milli()&lt;br /&gt;&lt;br /&gt;        &lt;br /&gt;    def milli(self):&lt;br /&gt;        """&lt;br /&gt;        Returns t1 - t0 in milliseconds. Does not update t1.&lt;br /&gt;        """&lt;br /&gt;        return int((self.t1 - self.t0) * 1000)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-3729024909463835726?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/3729024909463835726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=3729024909463835726' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/3729024909463835726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/3729024909463835726'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/timing-how-long-things-are-taking.html' title='Timing how long things are taking'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-824705067506464527</id><published>2008-08-03T19:55:00.000-07:00</published><updated>2008-08-03T19:56:40.698-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='keyboard'/><category scheme='http://www.blogger.com/atom/ns#' term='ButtonChooser'/><title type='text'>How do I check whether a particular key has been pressed?</title><content type='html'># initialize your ButtonChooser&lt;br /&gt;pressed, rt = waitWithChoice(blah,blah)&lt;br /&gt;&lt;br /&gt;if pressed==None:&lt;br /&gt;&amp;nbsp;&amp;nbsp;print 'You did not press a key'&lt;br /&gt;else:&lt;br /&gt;&amp;nbsp;&amp;nbsp;if pressed==Key('a'):&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print 'You pressed the A key'&lt;br /&gt;&amp;nbsp;&amp;nbsp;else:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print 'You pressed something other than the A key'&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-824705067506464527?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/824705067506464527/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=824705067506464527' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/824705067506464527'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/824705067506464527'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/how-do-i-check-whether-particular-key.html' title='How do I check whether a particular key has been pressed?'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-4925138284246909047</id><published>2008-08-03T19:54:00.000-07:00</published><updated>2008-08-03T19:55:27.767-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='troubleshooting'/><category scheme='http://www.blogger.com/atom/ns#' term='mac'/><category scheme='http://www.blogger.com/atom/ns#' term='quitting'/><title type='text'>I can't quit from the experiment!</title><content type='html'>Make sure you have exp.setBreak() in your code. Then you should be able to use Esc-F1&lt;br /&gt;&lt;br /&gt;N.B. on the Mac, the F1 key doubles up as a brightness control, and so you have to hold down the 'Function' key to get it to behave, so you'd hold down Fn, and then press Esc-F1.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-4925138284246909047?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/4925138284246909047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=4925138284246909047' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/4925138284246909047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/4925138284246909047'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/i-cant-quit-from-experiment.html' title='I can&apos;t quit from the experiment!'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-1854480129804223842</id><published>2008-08-03T19:53:00.000-07:00</published><updated>2008-08-03T19:54:10.208-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='troubleshooting'/><category scheme='http://www.blogger.com/atom/ns#' term='keyboard'/><title type='text'>None of my keypresses are taking effect!</title><content type='html'>Make sure you set up a KeyboardTrack.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-1854480129804223842?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/1854480129804223842/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=1854480129804223842' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/1854480129804223842'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/1854480129804223842'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/none-of-my-keypresses-are-taking-effect.html' title='None of my keypresses are taking effect!'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-4230021944006857361</id><published>2008-08-03T19:50:00.001-07:00</published><updated>2008-08-03T19:52:52.282-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ButtonChooser'/><category scheme='http://www.blogger.com/atom/ns#' term='lists'/><title type='text'>Creating a ButtonChooser with a list of keys</title><content type='html'>Because the ButtonChooser takes in a separate argument for each key, it's not obvious how to feed it a list. The trick is to make use of a &lt;a href="http://www.python.org/doc/current/tut/node6.html"&gt;handy bit of python syntax&lt;/a&gt; (see 7/8 of the way down for info on the *arguments kind of optional arguments).&lt;br /&gt;&lt;br /&gt;For instance:&lt;br /&gt;&lt;br /&gt;keys = ['A', 'B', 'C']&lt;br /&gt;bc = ButtonChooser(*[Key(key) for key in keys])&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-4230021944006857361?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/4230021944006857361/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=4230021944006857361' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/4230021944006857361'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/4230021944006857361'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/creating-buttonchooser-with-list-of.html' title='Creating a ButtonChooser with a list of keys'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-5042961687383082726</id><published>2008-08-03T19:39:00.000-07:00</published><updated>2008-08-03T21:16:41.195-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='indentation'/><title type='text'>Indenting code</title><content type='html'>Frustratingly, there doesn't appear to be a good way to tell blogger not to mess with the indentation when you paste in python code. The best solution I've found is to do a find-and-replace before pasting, replacing every tab or set of 4 spaces with:&lt;br /&gt;&lt;br /&gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&lt;br /&gt;&lt;br /&gt;Bleurgh.&lt;br /&gt;&lt;br /&gt;Update: Greg Houston's handy &lt;a href="http://formatmysourcecode.blogspot.com/"&gt;Format my source code&lt;/a&gt; Javascript app tipped me off that the key is simply to add the following tags around your code.&lt;br /&gt;&lt;br /&gt;&amp;lt;pre class="source-code"&amp;gt;&amp;lt;code&amp;gt;&lt;br /&gt;your code shows up in Courier font&lt;br /&gt;&lt;br /&gt;    and it keeps the tabs&lt;br /&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;&lt;br /&gt;will appear to the reader as:&lt;br /&gt;&lt;br /&gt;&lt;pre class="source-code"&gt;&lt;code&gt;&lt;br /&gt;your code shows up in Courier font&lt;br /&gt;&lt;br /&gt;    and it keeps the tabs&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Or for extra-special badness:&lt;br /&gt;&lt;br /&gt;&amp;lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&amp;gt;&amp;lt;code&amp;gt;&lt;br /&gt;your code shows up in Courier font&lt;br /&gt;&lt;br /&gt;    and it keeps the tabs&lt;br /&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;&lt;br /&gt;becomes:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 14px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;your code shows up in Courier font&lt;br /&gt;&lt;br /&gt;    and it keeps the tabs&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-5042961687383082726?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/5042961687383082726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=5042961687383082726' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/5042961687383082726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/5042961687383082726'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/indenting-code.html' title='Indenting code'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-1901687331666656348</id><published>2008-08-03T14:49:00.000-07:00</published><updated>2008-08-03T21:18:06.770-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='keyboard'/><category scheme='http://www.blogger.com/atom/ns#' term='response'/><category scheme='http://www.blogger.com/atom/ns#' term='freetext'/><title type='text'>Free text entry</title><content type='html'>This function provides a way for participants to type a response freely (with spaces, backspace etc.), returning the text that they typed.&lt;br /&gt;&lt;br /&gt;See also: the &lt;a href="https://sourceforge.net/forum/forum.php?thread_id=2150327&amp;amp;forum_id=548620"&gt;discussion on SourceForge&lt;/a&gt; about this very topic.&lt;br /&gt;&lt;br /&gt;Update: I just noticed &lt;a href="http://sourceforge.net/forum/forum.php?thread_id=1637899&amp;forum_id=548620"&gt;another post&lt;/a&gt; on the SourceForge forum that looks like it deals with freetext typing, but I haven't tried Aaron's solution.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #ffffff; background-color: #111;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;from pyepl.locals import *&lt;br /&gt;from pyepl import timing, display, keyboard, exputils&lt;br /&gt;import string&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;##############################&lt;br /&gt;def textBox(clk=None,&lt;br /&gt;            keys=None,&lt;br /&gt;            time_limit=None,&lt;br /&gt;            disptxt='',&lt;br /&gt;            wordHeight=None,&lt;br /&gt;            showProportional=(.5,.5)):&lt;br /&gt;    """&lt;br /&gt;    Allows freeform text entry, including BACKSPACE, until&lt;br /&gt;    RETURN or ENTER are pressed. Allows you to define your&lt;br /&gt;    own set of allowed keys and a time limit.&lt;br /&gt;&lt;br /&gt;    CLK = a PresentationClock&lt;br /&gt;&lt;br /&gt;    KEYS = a list of keys (as strings) that will&lt;br /&gt;    register. All other keys will be ignored. Defaults to&lt;br /&gt;    just the lowercase keys, plus RETURN/ENTER, BACKSPACE&lt;br /&gt;    and SPACE. To define your own, follow this example:&lt;br /&gt;&lt;br /&gt;      keys = list(string.lowercase) + ['RETURN','ENTER','BACKSPACE','SPACE']&lt;br /&gt;&lt;br /&gt;      N.B. Make sure to include at least RETURN or ENTER,&lt;br /&gt;      since textBox is hardcoded to wait for one of those&lt;br /&gt;      keys to end text entry.&lt;br /&gt;&lt;br /&gt;    TIME_LIMIT&lt;br /&gt;&lt;br /&gt;    DISPTXT = this will be displayed until the user types&lt;br /&gt;    the first key, after which it will be replaced with&lt;br /&gt;    whatever they type&lt;br /&gt;&lt;br /&gt;    [Based on the pyepl/convenience.py/mathDistract function.]&lt;br /&gt;&lt;br /&gt;    todo:&lt;br /&gt;&lt;br /&gt;    - it should log every keypress with a timestamp, in case&lt;br /&gt;      you want to do some kind of crazy RT analysis. For&lt;br /&gt;      now, i think it's probably somewhere buried in the&lt;br /&gt;      KeyTrack.&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;    # get the tracks&lt;br /&gt;    v = display.VideoTrack.lastInstance()&lt;br /&gt;    k = keyboard.KeyTrack.lastInstance()&lt;br /&gt;&lt;br /&gt;    if clk is None: clk = exputils.PresentationClock()&lt;br /&gt;&lt;br /&gt;    # we only need to calculate the END_TIME (i.e. the&lt;br /&gt;    # time we're going to stop) if we have a time limit&lt;br /&gt;    if time_limit: end_time = timing.now() + time_limit&lt;br /&gt;&lt;br /&gt;    if keys is None:&lt;br /&gt;        keys = list(string.lowercase) + ['RETURN','ENTER','BACKSPACE','SPACE']&lt;br /&gt;&lt;br /&gt;    ans_but = k.keyChooser(*keys)&lt;br /&gt;&lt;br /&gt;    # show the DISPTXT that will be replaced after the first keypress&lt;br /&gt;    # rt = v.showCentered(Text(disptxt,size=wordHeight))&lt;br /&gt;    rt = v.showProportional(Text(disptxt,size=wordHeight),&lt;br /&gt;                            showProportional[0],showProportional[1])&lt;br /&gt;    v.updateScreen(clk)&lt;br /&gt;&lt;br /&gt;    # wait for keypress&lt;br /&gt;    kret,timestamp = ans_but.waitWithTime(maxDuration=time_limit, clock=clk)&lt;br /&gt;    # throw away the starting text, and get ready to display&lt;br /&gt;    # whatever the user typed&lt;br /&gt;    disptxt = ''&lt;br /&gt;&lt;br /&gt;    while kret and (kret.name != "RETURN" and kret.name != "ENTER"):&lt;br /&gt;        # process the response&lt;br /&gt;        if kret.name == 'BACKSPACE':&lt;br /&gt;            # remove last char&lt;br /&gt;            if len(disptxt) &gt; 0:&lt;br /&gt;                disptxt = disptxt[:-1]&lt;br /&gt;        elif kret.name == 'RETURN' or kret.name == 'ENTER':&lt;br /&gt;            pass&lt;br /&gt;        elif kret.name == 'SPACE':&lt;br /&gt;            disptxt = disptxt + ' '&lt;br /&gt;        else:&lt;br /&gt;            # just append it&lt;br /&gt;            disptxt = disptxt + kret.name&lt;br /&gt;&lt;br /&gt;        # update the text - we need to do it in two&lt;br /&gt;        # steps rather than with replace, because&lt;br /&gt;        # replace doesn't re-center it&lt;br /&gt;        # rt = v.replace(rt,Text(disptxt))&lt;br /&gt;        v.unshow(rt)&lt;br /&gt;        rt = v.showProportional(Text(disptxt,size=wordHeight),&lt;br /&gt;                                showProportional[0],showProportional[1])&lt;br /&gt;        v.updateScreen(clk)&lt;br /&gt;&lt;br /&gt;        # wait for another response&lt;br /&gt;        if time_limit: time_limit = end_time - timing.now()&lt;br /&gt;&lt;br /&gt;        kret,timestamp = ans_but.waitWithTime(maxDuration=time_limit,clock=clk)&lt;br /&gt;        &lt;br /&gt;    # print 'You typed: ' + disptxt&lt;br /&gt;    return disptxt&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-1901687331666656348?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/1901687331666656348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=1901687331666656348' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/1901687331666656348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/1901687331666656348'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/free-text-entry.html' title='Free text entry'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7277754001418716339.post-4045424730513538599</id><published>2008-08-03T14:43:00.000-07:00</published><updated>2008-08-03T20:30:05.500-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='discussion'/><category scheme='http://www.blogger.com/atom/ns#' term='documentation'/><title type='text'>The motivation for creating this blog</title><content type='html'>PyEPL is great. I use it to code all my experiments.&lt;br /&gt;&lt;br /&gt;However, its adoption and its utility would both benefit from a wide array of snippets and tutorials floating around. This blog is intended to provide an unofficial home for such tutorials, snippets and discussion. In the future, perhaps there'll be some kind of official wiki, and these posts will migrate over there, but blogs are nice because they don't require top-down planning or impose barriers to entry when writing.&lt;br /&gt;&lt;br /&gt;I'll try and seed it with a few snippets and hard-won nuggets of wisdom, but I'd welcome anyone who would like to contribute to drop me a line at firstname at firstnamesecondname.co.uk, and I'd be very happy to set you up as an author.&lt;br /&gt;&lt;br /&gt;Yours,&lt;br /&gt;&amp;nbsp;&amp;nbsp;Greg Detre&lt;br /&gt;&lt;br /&gt;see also: &lt;a href="https://sourceforge.net/forum/forum.php?thread_id=2151151&amp;forum_id=548620"&gt;announcement&lt;/a&gt; on the SourceForge PyEPL help forum&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7277754001418716339-4045424730513538599?l=pyepl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pyepl.blogspot.com/feeds/4045424730513538599/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7277754001418716339&amp;postID=4045424730513538599' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/4045424730513538599'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7277754001418716339/posts/default/4045424730513538599'/><link rel='alternate' type='text/html' href='http://pyepl.blogspot.com/2008/08/motivation-for-creating-this-blog.html' title='The motivation for creating this blog'/><author><name>Greg</name><uri>http://www.blogger.com/profile/03614008835096850308</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://www.princeton.edu/~gdetre/greg.jpg'/></author><thr:total>0</thr:total></entry></feed>
