python-expyriment-0.7.0+git34-g55a4e7e/0000775000175000017500000000000012314561273017003 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/documentation/0000775000175000017500000000000012314561322021647 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/documentation/README.txt0000664000175000017500000000075012304063646023354 0ustar oliveroliverDocumentation ============= For online documentation see: http://docs.expyriment.org. A local HTML documentation can be found in the directory "html". A local PDF documentation can be found in the directory "pdf". The Sphinx source files for this documentation can be found in the directory "sphinx". An alternative local HTML version of the API reference can be found in the directory "api_ref_html". The Python source files for this documentation can be found in the directory "api". python-expyriment-0.7.0+git34-g55a4e7e/documentation/api/0000775000175000017500000000000012314561273022425 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/documentation/api/expyriment_doc_serach.py0000664000175000017500000000266312304063646027364 0ustar oliveroliverimport expyriment from pydoc import getdoc x = None y = None def _get_doc_and_function(obj): rtn = [] for var in dir(obj): if not var.startswith("_"): rtn.append(var) return getdoc(obj), rtn def _read_module(mod, doc_dict): doc_dict[mod.__name__], classes = _get_doc_and_function(mod) for cl in classes: cl = "{0}.{1}".format(mod.__name__, cl) exec("x =" + cl) doc_dict[cl], functions = _get_doc_and_function(x) for fnc in functions: fnc = "{0}.{1}".format(cl, fnc) exec("y =" + fnc) doc_dict[fnc], _tmp = _get_doc_and_function(y) def search_doc(search_str, doc_dict): for k in doc_dict.keys(): if k.lower().find(search_str.lower()) > 0 or\ doc_dict[k].lower().find(search_str.lower()) > 0: print "\n-------------------------------------------------------------------------------" print "[ {0} ]\n".format(k) #print "-------------------------------------------------------------------------------" print "{0}".format(doc_dict[k]) doc_dict = {} _read_module(expyriment.io, doc_dict) _read_module(expyriment.stimuli, doc_dict) _read_module(expyriment.design, doc_dict) _read_module(expyriment.misc, doc_dict) while True: search = raw_input("New search (q=quit): ") if search == "q": break else: search_doc(search, doc_dict) print "\n" python-expyriment-0.7.0+git34-g55a4e7e/documentation/api/create_api_reference.py0000664000175000017500000002444212304063646027117 0ustar oliveroliver#!/usr/bin/env python """ Create the API reference HTML documentation. This script creates an HTML API documentation, based on the docstrings. Importantly, it follows the actual namespace hierarchy and ignores everything that starts with _. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '21-11-2011' import os import sys import inspect import types import imp def inspect_members(item): members = inspect.getmembers(eval(item)) modules = [] classes = [] methods = [] functions = [] attributes = [] for member in members: if member[0][0:1] != '_': if inspect.ismodule(member[1]): modules.append(member) elif inspect.isclass(member[1]): classes.append(member) elif inspect.isfunction(member[1]): functions.append(member) elif inspect.ismethod(member[1]): methods.append(member) else: attributes.append(member) return modules, classes, methods, functions, attributes def format_doc(doc): lines = doc.split("\n") cutoff = 0 if len(lines) > 2: for char in lines[2]: if char != " ": break else: cutoff += 1 for counter, line in enumerate(lines): if cutoff > 0 and line.startswith(" "*cutoff): lines[counter] = " " + line[cutoff:] else: lines[counter] = " " + line doc = "\n".join(lines) if doc[-1] != " ": doc = doc + "\n\n" return doc def create_module_section(modules, item): section = "" for m in modules: section = section + "[" + m[0] + "] " if section != "": section = "Modules

" + section + "



"
    return section

def create_classes_section(classes, item):
    section = ""
    for c in classes:
        section = section + "[" + c[0] + "] "
    if section != "":
        section = "
Classes

" + section + "



"
    return section

def create_classes_details_section(classes, item):
    section = ""
    for c in classes:
        definition = "".join(inspect.getsourcelines(c[1])[0])
        start = definition.find("def __init__(self") + 17
        end = definition.find(")", start)
        if definition[start] == ",":
            call = "(" + definition[start + 1:end].lstrip() + ")"
        else:
            call = "()"
        call = call.replace(" " * 16, " " * len(c[0]))
        start = definition.find('"""', start) + 3
        end = definition.find('"""', start)
        doc = definition[start:end]
        if doc is None:
            doc = ""
        doc = format_doc(doc)
        section = section + "" + c[0] + "" + call + "

" + doc + "

" if section != "": section = "Details (Classes)

" + section + "
" return section def create_methods_section(methods): section = "" for m in methods: section = section + "[" + m[0] + "] " if section != "": section = "
Methods

" + section + "



"
    return section

def create_methods_details_section(methods):
    section = ""
    for m in methods:
        definition = "".join(inspect.getsourcelines(m[1])[0])
        start = definition.find("(self") + 1
        end = definition.find(")")
        if definition[start + 4] == ",":
            call = "(" + definition[start + 5:end].lstrip() + ")"
        else:
            call = "()"
        call = call.replace("        " + " "*len(m[0]) + " ",
                            " "*len(m[0]) + " ")
        doc = m[1].__doc__
        if doc is None:
            doc = ""
        doc = format_doc(doc)
        section = section + "" + m[0] + "" + call + "

" + "" + doc + "
" if section != "": section = "Details (Methods)

" + section + "
" return section def create_functions_section(functions): section = "" for f in functions: section = section + "[" + f[0] + "] " if section != "": section = "
Functions

" + section + "



"
    return section

def create_functions_details_section(functions):
    section = ""
    for f in functions:
        definition = "".join(inspect.getsourcelines(f[1])[0])
        start = definition.find("(") + 1
        end = definition.find(")")
        call = definition[start:end]
        call = call.replace("    " + " "*len(f[0]) + " ",
                            " "*len(f[0] + " "))
        doc = f[1].__doc__
        if doc is None:
            doc = ""
        doc = format_doc(doc)
        section = section + "" + f[0] + "(" + call + \
             ")

" + "" + doc + \ "
" if section != "": section = "Details (Functions)" + \ "

" + section + "
" return section def create_attributes_section(attributes): section = "" for a in attributes: section = section + "[" + \ a[0] + "] " if section != "": section = "
Attributes

" + \ section + "



"
    return section

def create_page(item):
    modules, classes, methods, functions, attributes = inspect_members(item)
    trace = ""
    parts = item.split(".")
    if len(parts) > 1:
        for cnt, p in enumerate(parts[:-1]):
            if cnt > 0:
                link = "" + parts[cnt]
            else:
                link = "" + parts[cnt]
            trace = trace + link + "" + "."
    title = "" + trace + parts[-1] + ""
    title = title + "
" doc = eval(item).__doc__ if doc is None: doc = "" doc = format_doc(doc) doc = doc.lstrip() doc = doc.replace("\n ", "\n") file_path = os.path.split(os.path.abspath(__file__))[0] p = os.path.abspath('{0}/../../CHANGES.md'.format(file_path)) version_nr = "{0}" with open(p) as f: for line in f: if line[0:8].lower() == "upcoming": version_nr += "+" if line[0:7] == "Version": version_nr = version_nr.format(line[8:13]) break page = """ API reference for {0} ({1}) {2}
{3}
{4}
{5}{6}{7}{8}{9}
{10}{11}{12}
""".format(item, version_nr, css, title, doc, create_module_section(modules, item), create_classes_section(classes, item), create_methods_section(methods), create_functions_section(functions), create_attributes_section(attributes), create_classes_details_section(classes, item), create_methods_details_section(methods), create_functions_details_section(functions)) p = os.path.abspath("{0}/{1}.html".format(os.path.split(os.path.abspath(__file__))[0], item)) print "create", p with open(p, 'w') as f: f.write(page) if modules: for m in modules: create_page(item + "." + m[0]) if classes: for c in classes: create_page(item + "." + c[0]) if __name__ == "__main__": css = """ body {font-size:100%; color: #393333} a {text-decoration:none; color:blue;} a:visited {text-decoration:none;} a:hover {text-decoration: underline;} hr {color: #cccccc; background-color: #cccccc;} .title {font-size:1.5em; font-weight:bold;} .title_call {font-size:1.5em; font-weight:normal; font-style:italic;} .definition {font-size:1em; color:#555555} .section_heading {font-size:1.5em; font-weight:bold; color: #d14836;} .separator {color:#777777;} .module_name {font-size:1em; font-weight:bold;} .class_name {font-size:1em; font-weight:bold;} .class_call {font-size:1em; font-weight:normal; font-style: italic;} .method_name {font-size:1em; font-weight:bold;} .method_call {font-size:1em; font-weight:normal; font-style:italic;} .function_name {font-size:1em; font-weight:bold;} .function_call {font-size:1em; font-weight:normal; font-style:italic;} .attribute_name {font-weight:bold;}""" css = "" cmd_folder = os.path.dirname(os.path.abspath(__file__+"/../../")) if cmd_folder not in sys.path: sys.path.insert(0, cmd_folder) import expyriment sys.path.remove(cmd_folder) create_page("expyriment") p = os.path.abspath("{0}/index.html".format(os.path.split(os.path.abspath(__file__))[0])) print "create", p with open(p, 'w') as f: content = """""" f.write(content) python-expyriment-0.7.0+git34-g55a4e7e/documentation/api/favicon.ico0000664000175000017500000002267612304063646024563 0ustar oliveroliver00 %(0` FFF999A888999:::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::999888;;;jAAA OOO???r;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::;;;DDD HHH1<<<:::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::::::::::::::::<<<_>>>:::;;;;;;;;;;;;;;;999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;;;;;;;;;;;;;;;;;;<<<===;;;;;;;;;<<<:::@@@GGGIIIIIIIIIJJJJJJJJJJJJJJJJJJJJJJJJJJJKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJKKKJJJJJJJJJJJJIIIIIIFFF@@@;;;<<<;;;;;;;;;;;;;;;===<<<<<<;;;GGGMMMLLLMMMMMMNNNNNNOOOOOOOOONNNOOOOOOPPPPPPQQQPPPQQQQQQQQQQQQQQQPPPQQQPPPPPPOOOOOOOOOOOOOOONNNNNNMMMMMMLLLMMMFFF;;;<<<<<<===;;;;;;===<<<<<>>===CCCLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKBBB===>>><<<;;;???>>>DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKBBB>>>>>>===<<>>DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKBBB>>>>>>>>><<<@@@???DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKCCC???@@@???<<<@@@???DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOONNNNNNOOOMMMNNNNNNPPPPPPQQQRRRRRRRRRQQQPPPQQQPPPQQQMMMKKKKKKMMMNNNNNNMMMMMMLLLMMMMMMMMMLLLDDD???@@@???<<<@@@???DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOOOOOHHHIIIHHHFFFDDDQQQQQQRRRRRRRRRQQQPPPQQQQQQKKKKKKGGGHHH>>>MMMNNNMMMMMMLLLMMMMMMMMMLLLDDD??????@@@<<<@@@@@@DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOOJJJ[[[tttuuuhhh;;;PPPQQQRRRRRRRRRQQQPPPQQQQQQIIIwwwxxxsss666LLLOOOMMMMMMLLLMMMMMMMMMKKKCCC@@@@@@???===AAA@@@DDDLLLMMMMMMMMMMMMMMMNNNMMMOOOOOOOOOJJJ{{{SSSLLLRRRRRRRRRRRRQQQPPPPPPQQQQQQ\\\FFFNNNMMMMMMLLLMMMMMMMMMLLLDDD@@@AAA@@@===AAA@@@EEELLLFFFBBBBBBFFFLLLMMMMMMNNNJJJDDD@@@nnnooo>>>IIIIIIJJJKKKOOOQQQQQQPPPQQQjjj???LLLNNNMMMLLLMMMMMMMMMLLLDDD@@@@@@@@@>>>BBBAAACCCgggiiiGGGMMMMMM^^^bbb~~~zzzOOOEEEPPPPPPPPPTTT<<>>BBBAAAEEEUUU???KKKLLLNNNbbbGGGOOOOOONNNdddWWWCCCNNNLLLMMMMMMMMMLLLEEEAAABBBBBB>>>BBBAAAFFFIII^^^sss@@@MMMpppFFFMMMMMMPPPOOOMMM===JJJMMMLLLMMMMMMLLLEEEAAAAAAAAA>>>BBBBBBFFFLLLKKKhhh^^^UUU\\\KKKhhhiiipppFFFPPPOOOLLL888MMMMMMLLLMMMLLLFFFBBBBBBAAA???CCCBBBFFFLLLLLLMMMsssGGGOOORRR|||FFFMMMQQQ===OOOOOONNNWWWCCCMMMMMMMMMLLLFFFBBBBBBBBB???DDDCCCGGGLLLMMMLLLNNNvvvHHHPPPMMMMMMOOOOOOfffkkkHHHOOO]]]@@@KKKMMMMMMLLLFFFCCCCCCCCC@@@DDDCCCHHHLLLMMMMMMLLLMMMNNNNNNPPPNNN~~~QQQNNNOOOUUU<<>>IIIEEEFFFEEEAAAFFFEEEIIILLLMMMLLLOOODDD]]]BBBeeeXXXNNNOOOMMMIIIzzzAAAHHHEEEFFFFFFBBBGGGFFFIIILLLMMMMMMdddJJJSSSSSS}}}gggOOONNNMMMKKKiiioooEEEFFFGGGGGGBBBGGGFFFIIILLLMMMMMMNNNNNNKKKLLLKKKOOOOOOPPPMMMMMMMMMPPPRRROOONNNNNNOOOMMMOOOOOONNNKKKNNNSSSNNNMMMMMMKKKNNNNNNMMMMMMOOOLLLKKKLLLMMMIIIFFFGGGGGGBBBGGGFFFJJJMMMMMMLLLLLLLLLLLLMMMMMMOOONNNNNNNNNOOOOOOQQQPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPQQQPPPOOONNNNNNNNNNNNNNNMMMMMMLLLLLLLLLLLLLLLIIIFFFFFFFFFBBBHHHGGGJJJMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMLLLJJJGGGGGGFFFBBBHHHGGGKKKMMMMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMMMMJJJGGGGGGGGGCCCIIIHHHKKKMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMMMMKKKHHHIIIHHHCCCIIIHHHLLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMKKKIIIIIIHHHCCCJJJIIILLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMLLLIIIJJJIIICCCJJJIIILLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMLLLIIIJJJHHHCCCJJJIIILLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMLLLIIIIIIIIIDDDKKKJJJKKKMMMMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMNNNJJJJJJKKKJJJDDDKKKKKKJJJNNNMMMLLLLLLLLLMMMNNNMMMOOONNNNNNNNNOOOOOOQQQPPPPPPPPPQQQRRRQQQRRRQQQPPPPPPPPPPPPOOOOOONNNNNNNNNNNNMMMMMMLLLLLLLLLMMMNNNJJJKKKKKKIIICCCKKKLLLKKKKKKOOOMMMMMMMMMMMMMMMNNNOOOOOOOOOOOOOOOPPPQQQQQQQQQQQQQQQRRRRRRRRRQQQQQQQQQQQQQQQPPPOOOOOOOOOOOONNNNNNMMMMMMMMMMMMNNNJJJLLLLLLLLLFFFEEEtJJJLLLLLLLLLKKKNNNMMMNNNNNNNNNOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPPPPPPPPPPQQQPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNMMMNNNKKKLLLKKKLLLLLLCCCRRRDDDLLLLLLKKKLLLKKKLLLMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMLLLKKKLLLKKKLLLLLLJJJAAAA???8EEEKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLGGGKKKpVVVNNNEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEE@@@AAA0gggpython-expyriment-0.7.0+git34-g55a4e7e/documentation/api/Makefile0000664000175000017500000000015512314514263024063 0ustar oliveroliverhtml: mkdir -p _build python create_api_reference.py mv *.html _build rm *.pyc -f clean: rm -rf _build python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/0000775000175000017500000000000012314561273023165 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/expyriment_logo.png0000664000175000017500000012034012304063646027117 0ustar oliveroliverPNG  IHDRXsRGB pHYs  tIME  b IDATxwxUEǿsj(XXW]vm`cUtEQײVV{WH!$sބ Ƚo>s03s)owfD"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D &E H|3 06yD Az)ID" ,D"O?aqq98@$ 1"vBd,f_ MSE'_tWRH$A"H$T%ž, *d܌]`I#K"HK"מŹD0VjT 0j9x҄q~%Hd_X(V:FLڼe_n;SM%H=M˂cH㎜Z /T9oJ$^yL俳kuM]+*ӱ尯pKΊY\<JF$eJh4ݎ-ܘ`JJ${3dbE*zH?[H$He p:mv(JA'^vD#XrsD迏10𤧓 1jt@.\iC&R}idIK"!!Af\ؚ_&K,Yv.ҠDψ#e`vnW(CDBsCp*3f`u%Hp!eeBaJ˃`Y ƘQQUyrDy煙 +#=y Xf-9t/].mC$ ,DG+De2tJ$2L᠏ūbp1 do:K\<0ګN8 Hf/uP_!i`I$?>CT;E(u[1ư@ѤY!+=zzRZN~ c |mYhޕ``S Hp!4?_l9!1UVA5q\"Ho<ϫ,˺[LQ~3V?3!jUϝ!i`I$?^|7?yhiAQUŲ`khZ,. ,d3^5M둞Ņ(,Ume!B`uk7T~t\"H$H"qG.Mv'=i @DRTe#7B4O|K$xFpΟ$a}kp sA(߸vm ѫ4Z[C\~e'6w~1y^]NXd.Z1L!vZHq=F=R:Q%;v?L'IBc23Ӕ!L"D A"eSc#Lc,f`m}5/a 9Y/`Q҅@yHlDLeqK]aX AUQ|`h!"chb` \:o;w^}Dt)QoDΔkص>XqePD5 Ӳ^"nƜ(Dp!d wEcUD)Lo3{Ac=8ʱ 2k%S62qGB$!ͪo\xb(vo߿!8@gHo]#@MQ Ԕk_/{u!D4"vjr2 릿*9wB*GDde(9_( D8Dr.{n3[mjY\B;[s!kX_nX%X}nJBPFy|yж/9B !~Ag @h7hHf3{xSvVvB`ſxeo(9gZ/l0!!<`pi0E3X . \q?v̙=!he<< C*TU [^knYv͙=}$"Dž2v&SOaʯBoص7gøs"4*)pg3!DR` uu,^r]rB)nK4iEQV74_{ݝ3{_nO$~i֝v!a+Q ! (:33Sk` H@B{@0 cMSb ( B97x]"gd:Ms, \D[mW'>\00,oI}r=ϺfbL9LDk-5y}Y/r_~쮮_{iY9Lj{lN ?$L,J^k&xi֝N r33`&,!eĿZȴLkZt >Pk[]]Χ3, i#m#[LU-};k~ނݘ,Ҭ;Gz2ҳMiֿ!3;˗/CCC7Nˏun{<'+"goE̘mצ0eE3蹇q330,~4OY̦c pΣܲ` ÄiY[ Ω)O0KA`YwRe՛ ΣWwoug 0#y7ϘUQٿ4QBE)Tԡ.(A ]pm3AI:wv- X^}Żxi֝!.d'"[۪ i8v0b "p!b_ND0L-~"B- C:ch4뮏 Mջ ]5+cs1gL`=9T&UUEvfu4݁־+dž@\ HPiw>1yG`o~5<R"96ׯ9Ֆk?>>=г[r2r:Mhj<6ߝ#nv!-Ń|gCf偢j4MA4(,jҲ8+ 2"a@8" -- )*CaD V !}ŭB"|_Ᏸt:p9t M|o2>B4w؉ؚ`A.lT^A$w?-V0_Y{%` G:~ֹ'ܾܲm?ъ no꼴%wƠ3-R2`s8zMxPj*7͛A&ޗW|s3>oN;{ (|F,^=-_='<ceaHqfgt]9fwzu-ċqeοMki/u3]זAC+%$K ͘8嬋н;v;"j;R$$j*oS56My9oĵhg<_%=wѣ'Lӊ:rijŪ% P__Eak7T賯m~nߟcvC>9Sjvp!@(M, CUO1i~oͳѓ cfG LlQe܁IA?~|M[hu,CeʂP8r v'\6-͓̼|fT;TU˓FϜTU vQyD !`HH   3@߂-E"TJ_nyමM -s==?Δ$oذf~5y qRsR3YTUYpٚq/Xy[U"e`Y?qHP "4mM(_x4Yn' 醓N=3֬ZWޖ˟ޫ|w>X{f<OR2O>|7-v&&"[)D'ykZk/ vXQ5,luO9#< ];U O=rTVm}Нޕ̿;EaKg^pq/cy۟G-^vu,zc=Э6"zT8KC${NaC`1fwDM$ړ`&eb˪c +3#~[xw3f<0Lo|Do;ُ ¼y +71I EaOo>g6][)D}8)OE *ݏ窦f_I<ЭYvXגAIέCD`$b/?ͦP823zeG>t9DxnӨ~>Ɯe٣0 6Z?}O`iڴv?g6J?1`x:dl}Xp w074sG=9J|+W%) D|bz?Ueeg#-=w*l7as8c3ֶS^ò~v{h9S-8ᙇnU~K (ŘcNcwZ6/](oqv}Jl 9dι:֭g_6J)߷Wt)/^o@(|^]ٚ+ DꕓvO+he`Z{rrڔZ"aUE}7 o짅WK{WH\QT5k=15+32 zbK]|y >P6ly[yf˽ 3쿟L/)ACS3r@NL]x=5xc#aF IU/F}S3`:wy)ؔk6k맄p[cϋHTbM-9&ݞ  m]׻G==6exSz55@?ysOH$7 {4KʧP8rCnN<ީ-1NƟ  ^{""k[9vc]Ҷ̗lيL8 '9!?>/;~ zG> V2bK.Dk{G( Íaqױ&oߙ:l[|eܩ@paK}cz?[ث?u/ľk>[)‘ȕ{6˜EL:`)=|< nΘa0xQ؄-lFIXFe᝷B ĺ±V7Ӟp:\.)yw- 2pwFr8 >3Ϙ|ąQsosK#suQUӴнdffQ9@N^7ZZs8KMӄ;-cd硈eqJگ MjiZu1Uږ'BQU\|mAHݑ%83]:aٲvbe }JK R*oV5MmMSs-vALSfP}5ل LKnKN`xݷa52 jL{-Sp&o Ng,PPKzI`os:0;6N_Xݮc術`;:.'EAC}=~TūP%hMPTܧsrᤓO-mfY:D"z`(<#$WZu;w Jv%=>%0``{7/}a-@)CƘdIMߣe #FEA^ !cX]4ssQfww N<Ck: ݽ8w+ɺ0Y zڙ I}7:y[5 0@)i{tNF>MM CWUz?N(, >pP-T}Wj1UTc;PXLSxKa|(/G$} _r uLה?-^2FDo=))`_iiiS'cⳏ4t+kퟕ%S 03<(۸.*}KaqΜې*G9w@ayEsV.Rbؖ5q%ڬ !d!ҳ;-ߜ.EޣniիV0L%|)]t]nOe h ci bj!-#S߳n .^{~S \PܛKse*j,/Gnv5e%>|6*>tJٯu*p8]0dFѱNDݸՃ1TO=#䥨 Rӡv ]lnq~`Ĉ.W;1UUCsKiHs/UU0|1-Ld!dgߑw?- a 3@ uI}#GCUR=dm]gXCe D]SEMki4"Ѐv۴S2mD#A))8ܩ{>*J{ N7kapUtΌ^FFz 8 ~ IDATay+O?U@kW+S}; [.Ҹ%r;-[Mw[;}TZ|n}D^^^}P]kvMS Ssl6{{1ns^Hc\ 8p,c{-ڐۥAp_Z;{Ne.Elrtih񇐖(.?v%8McÑ&Nګ#Up8Y X{,n qRi 45Z~y\%bY}ֵ] A !< %+S;]vj*((J8a0xHZ޹zo?bt뚋c&2]%6zƈUV?r]Lhios~hv8_4xaYn{)l4תukF '~]ZQS[ r3lqɕUwa]< d567 ".'&tGgu>27 6UٙiiA`ѩPB(j=klt"Xt:l83`OwgBbe ,. 4}^vꄢgLS/e1rhTN=Hv`s'SYYs<lӖY^ߢ"n~)OsA}̜۹* ʫ'سw_Caxή`qyƀ1GNU@(lHO)αXt$t}0 )9Gà0u.21@ CDpޱC.E3meBXiKKXu[~*%^;fY7eY:Tc+2^}T }La4 8 t˭ݝKJaJDiE%T.,K{0v}Aff&mjA[`/,CnkW>^i`3||^ߵ[I>cXFn45hJ@15f4Eu)fKʧj|ֿ82aTmqޑ6WfgTW_ Jl1r:w RHsSbB&w~y63meٯO(-!w,ChC߂Ͻ)Tc*Qt+e}B\.WRPT Vpv.T]_tmW4vQ 00~J6oѼvY UU M./ۈz}blC}]-Fggtpݱ׮kij8! !]L)` ò+BSU<)m={Q7neY¡֟弉YPɀa,Yٶg˖.ImאqZ] Vlz*E= }9tS64lWzşt;%3vSF[`ӦJ|7Xx1|~?2220ꠑ8CPTTҖ`[eWC c.Iuh횦!;'+Ss}͈D"|9 |-6nZ0[),yKg`7\[_X^i:M< D") zV-_r҄%zeuݼ@(RaVgiJ۳`.㖫Q6JԤܻ 6O?ԡ=/=g3䟴ySz)IG]fM8ͦGcB ^88G/0^bʰ5=N(H !xRwv~ƒ%? pݻ$gvIKπŅP[ҴynwEַ_ݧ/[l]㢜Sv]/yqp .r69lXrؚnoa ;fPԣoR}i[cpԑns9$vfӡ hbwN#e+kai_8ܑX?0"n!EaHlo;q[PMS_ЭSiBn&|A6]c ,0P,#+3vun ތg{[2|)HKBIi?uyШR烁Q0 dMqȑ'D/MBDo9)'k6T} Q. æfg?zҿ,r2xpfRmrnݣ,t[Y4lIuv>bt8:4MC]/=7B(6;(,,SWWnPMzSa_~.0|#zi|rZ 'L:vd؝)(-իVY+UnYטE8m/;D@3Z#U1w<|ؼBp86z\puX\@#GBw~'8hn1lv8猈[Ucv5JK݇>q;pre' \\ХG1xQm*3%M "}TY8$3&[˶,#G'#gNms]+UΏyatR\](Vo G:ng"}@a jȲaw6.wi]ZyЭ7SAŦ4(El:K5-3ͣf,I# KDB_鰁[.9ԭ{uc |o"S=Ƕ{ƌV]Zv鰁=?үLJK]:֣GSף&*1l]Э7u1kد7U/mhq:lj9iRS\2f>ʒ,**X|mŜIGkj& ܬsUU/p'CB[媪VT͘b2uAa+S\?s5,:զk>G.*O[zu,[mZU"º}jS>rv4'ɧXg'  sA"~c#gޝ{͔7†+]yS~1G``Ou`#eÜNr'<ޛ/㲅QaZ²MuKbpĆ-wܨuɜofee\UPBl]}Sㆵ3~NgRCvn|ކ.cF5N')kh9NpR2-k u5kVC !5IpQ0#S(L4x}ؚ%vNICfffRE\QUY4M-ojSvCI|=X]cߦa!8H>5ITuӽW"%,XQc*&d%K -GKk2̨mVж}:{)tΌ5u[*l3@c6ΨlO1EAMOjeK @pn=CMQ&ev%=Fc*1֦:f=mX/s_)<|Ǣ/=1`iX8I-aYj^jV&:t {&‘֯]ŜN[CSs`v 먄BQXߺlْ֨w;4s>A0n=KXg"hBRUXNK:9z,%MSYrwk E*rDtiٻ_KYY )#{$Aa`Y5PQXfZPxe+$`յMż2v K$VWqg"ug^zS}מW) -ݬ].:J #K0hȤ'TunIKłK V| @N3ɟm5deqqocy]^M͍y.--2Ģmil6w TU|c/c6]kZ_^!e n L̇%l- *,Emr8=N(Pcs`-4ޡ+8k^ܫ3sҖ|HEe.1/4EauТqסp:I/VVTE%=݌aPqp8I׳<Ujj/Ɯ vs.<&Bk~W"MM3.>}Br#TPani_,k,:56(`؂6f9I綍b#6ժaP3C>fq6R&9, IDAT8p"  L9/19o_0bĨ~ n$b3O'MC/y[?ǖZ/MH Ώ-EGy .[z'9QԲU(BKL G"7T։CG| вdECVNkhKs`Ȱc<,x" >bvKHB:4 7_BlcrԱ}z ϔD! Ĉ4yn7p~jvN>l6=+-031kЁ܅[>;e`U0L蚺a;|W263Ysx^޷vI.S1#'v:tÆ.+U1kҴR ]+3eyp×YRJaޝswHU~Zu43~0$?3z0ֲ> 9_|RF4f!BR; K#;'ʦeҋi4'=7ge9v Av t'{=^JwL}=0 R}/iǭ};Ųe+2/X u7! i;7W[f,L5?bOضȊČq'mKi"a)Gq$o!n'_޵c _|}?pz9r8CG1Vwe>u<6,SO~n{p+=Td/gflvl҃; ,Վ[#-0ْ2>-MʛZ _җ88Cޟ#H>|9(JvK3je! uB()iapphqrq$s;WďD"p躭y_:|;@wCs{w "<'%B,ǿ?v:4[qq> { ~C)FGk6ɹXf)i3擝oNzs{IM8kB"Ќ9KX %DؿK)(J>f.:ckfgGHxmB&y}愲 EcF5gC]Hi3fVUi'f nDΧ ܠًJet)6o~DNK2XxspڃO /s&z1;(=iH$ppxfbÃb]B# F/WsY@4f6Tiy\haUK3c_ױi҇jۿsIiw@5K I3z:Ij"ϫY1wVEv\ޭ-- "1V_$觱#-Mfk AY sEMKW0P*}(|c0Yz)% J?|jRjjytv:4a4+IN8/ }`L/#"N p#5a,M>p5 R x4M/xbz!v;3ta¡kV{T; b#Nۆ( H3L0d&瘯m;800>v>]@B|b fh cx׊2)eWP,K/>O&c4][%WC f ј:2>R1/l%mm Cq$ݙP(dЯ4..o?gvц枿XAɂZ]wR~Am!q Bprf̶y=TTϰm :],|mQ]]cKу{K5MLC]N]}C)̌GbW:7ui$'XS^(+clNdzx\. l-m])yѭo6rMB{k4)!+&s m߷GG[3KIS5~-KKI L±뿽 e"n9Y\N*f⿊.ӟe/h}wVtdt¡ ::Y4H1| XzD!c(BB&so~PgO`\XDpc?'.WFȮWBF>z H ƈ`fOW-T^Qa{|]x)Bw_DcF-%.= .h4Mt2[^E U\<[nt:\oqsߞ$0bq|/%K!N8m+w7Ё=QUX+[53S^؁ǯөw +J@YyuF(,`1 YLj΄Ajlղy f ޳:G)URX\̺n/*RnH7-ZHhWo5$¿U3JSC~aIF;CRC3^|ߝ˽DbҀ݂ ΌYTTTZI+g~A%ζCE$ 岇Mmle0-0HB>"4Kl+K۷JP GZ./i[i`=s&.;sMu5ٹ99x< kR.BVbxͿoS/IJ#rR 6{޲jrC)`>fj:&߷Dό:G)f[L{zIڙG/%D͌yP{6h ϤnJ>{! ~M R Iɔôlm%} |zGȗco999ޞ>m8e3SVP&yǎ&)u1E)+(q14iDch1W㧿߹^һy,[ 7mt^O ҸuFru DhpiVcH!𪕰.!G'ե/1NٽkIe n q[Et8m&8]_BTuH'D'ŽeeB80H:!h93P^Y ˲ l}-phRcQfe嶭 6߱5v:ǝq̜ ¢GT8chۥ!/v=!AJ:89& GYפF{ɮ b0mRr/] Jh4Z([:v]˫f&2#aŴ忸 @ XRR^CRg–i[lw=_B[nw0 M``tB9jLD:Ӄ[kyu7`(-Ƃ%R03b8>Op"8Gv@@y4%sIkH1'$ͽf)EU2H1`45!)?nbz13fYLv[z# P]r:N;wU4)_%ȵ]rAdfBפěK 5MJwAa! ] HgϜ"34Nt5]bz`˖W$E#ExcV&#Ac{? OԷ *-yQL&C\jJ@_2Ʀ&iF0~4hj%潤e+_J)Wp+IrTxDqA`bx]*δG@@ܰ@RSHT}:WVϲEJHz#H rȡ}(.x;obxN95laK9"" ǐ ;FtmnAiFǦW_ Mhʹ=]@"|zHy P\Zn{oL™4DT}u5Mjf.M>X@8gy*/G[xC ACvtAꆩ@(ޗpR+;.^1\.GFygatMyw"ۤ ̞; `S֤3FhRR%W NظEk@U_ Tx\v8dGuu R'nQ)"̪e0@$j=piShE"%-'>x|oMM5_^RJATq{? n"Uڔ./$d,h &Ahi߅(iKRXq;!%Mܟ@&%P8z:?ooitIR k`鈽{;1`w_xuMR@'|:4% Z%\e!4!Cqi:B\D(eL+}+ 4e ɲ\^$aw˫ fD;0br? AKߐD#$"RދBʇU\;}-[^ wd]r:ϵm`uvv@"ŪI)I"%t[YJ!3Ǜ7EFg.cC'>xCbBAHIj/!!c0Ͳ͍ 8tDQ.lg]CKGK g` T"n-cc]if>DR}.|7;:J)l`}7>zGLLx %KIpڽAݰ}̘;ꄰ8~ !A`tfܼTOev R}?n@-==(,*HhƦfԟ-!@s{aK7LTN;rx~03{Lރ%h] bu+K Q\4L7ނ4c:oNNW",Kkr|9op*ųr ? [wF)ƙuĞ_ ,]m[Ja׎Т܁DIz[``<씭O}K_d?nd۞nD"P"!hi>gU  @8Ht3}-؇[m߈[>!W[06#}#!6XzǏBzb'0&bR~&gJlB؂ C, o\0thwjDEp+4Ra UTM'7}YfvWQSRťLBچh8aoӡ͔F-`fٵNf晤.hu..xSM\I]MD&pb/D S,ť̶#ǎ¡K FhZX*gfg`mmFp:dBjW D`ah\Y}\|vy=((,YKQMC pz~U\?t6„~R%UKIaǔdzn0ftamkQVJZ9ׯvipcGB–8؞ SgBd'UbHG1kY `tw$b,0yFC2L F$17U#XFu*.|?RxuӋi2ZS51)U}775"7;@a! hB\WBwW!/ vko.7YQRVti*vF.3**(,%@:躆%fT2UVQcAq>3*K+X$<9yi:H۬x k )<_ճe]z5߂(-yةMHimC8|tiy:>-P|,.,ԟ-+0mƼ /<$OdQ9Ep9uۨgnjMJ{#@[”jM'"E BqImB,K C ȺH!nyw`'UIȴd E`}H nQJaڜEdG`?09Q9ޡf.efرD@s[6.KR 8sԂ_'PXmԤr޾8Y* )&B?z/3ow׬Nƾ񓧿KgA;ww Nkw}7J+L'ER ;/5M -ͧI0  > :]:yGmRH+^k首W>$󸞪?:}9G=ҟu;5;G5BwqymWRܒtG1W$n7,"[ICjyX$XAIymKR +?-m+N˿R^P8Od0[%K ɘ~Z?PU=׃|I!.2Kt. ,%%CIi4M}yqVNq(KaW04P0ƆmI]4mmdʂz)4@(g$~z0c*!L ,[3Z[ cG]:c3IikeBРR<=z܎uM`Ɯřet=;tcÜq)VeegAfm[_3LSf [本\AnY]`85H("eP]=e7/.{ż^oDwrGIavb |?/Yy3J+Ҟphhl*q.4tͼ z!ʂH|h('xczۼnǺx{AwWJJKv/]_ۺٹt^ǚFkۓ5:M^1%4m?fϞ VJ_龪 ~k'Z^LXi%on:d~[v6 Gy=vyG'tl׳@Q{<,hQu]LD9:R$0NK=n i08JX̵#-L*4ḦFD$CX7R ~ ei·}fh DbFO{ )Re `0p$‚FFcL)E i7`|a;o^ C%vqvBWO+WDX6(rσ%+Y̙&deܞ|MBw1*(  XJjX̯|T䀭7 {viܳoTV":9D(pH=P(fifWα,=TuH䬾ox Jكxͯr`(xOyǥ3 4H$'Kfc0m7LC0uI;~V)~|ZM%/Z+ũ=n'F,DyΟhR!'˳(*.L[/^^2rs<7x%yYϛ{d)DVOa޽PJa09FQA MiU~[(ey] jiZeI{-tz ,& JAGQaK+HS{hA0#F$ˈ\T0i8<ِl@RG,nP ԟ8 3M92?mךFQ^6/YN,tw\.!!xɩk_9s/_6R6.R'_`8GLI !PQ9 DS ;vlRY/} K98.Ƕ0݂ZSͫn ^)Ag<~"i`yX񦎿}lb$1$_3>::7 DR)+CիΆ招:zo)'˃Ӎ]_KWmIF`3}M GʕZ bƚ+/cϞi&9~~Ⲣ)SY4b&u7qq%gaF0_{\tSt\r;g~7w| g5aηءKu^؄P~6aY c]wЛ|K}{wY 7- *ε̈E8q=ϛCnp: `wmXRHT\EӗGʦqF}|e|b`jlRe+?*q,49MR` >j_xlXˣ hu703[u m̈M%C%ArSz>z*(I(R ݻ N Exs.hq^Q%4)y7^/ns儮)fΚ3&  %-b ,,z:]L| +?J)^Do!RӈHRRB/_yhV@hҸu>y ,]$T(ζJz}HTFšndfOIlx:huavH@FggA')EP6iLJE|[gxt+8\{[ɡk-!>>)Zt"U-\.7}4Mt&e0~`tRs4MLDnAM{ 7'ɲ6 FKY@4j tRUJVL@$fr:Rw|; AZ,f0bPJ\1Dh煮c5%b{>}:  &̩'nXUρ9F _; Uw3̘0~2nR@,>ufVCx ~,@ {peupjg&4M\CDDhl;ēe}C)^`b.vUef vP0{c-<#7|:RaaaZP1cܹ$/ M1VX0_e$\oA8Q +.swF.I-MӰdō {N\N GN|yd'5q<7R)ӒH$H$BBP4f Ņ- TTMz`0 H% JA@p EN28v/V@z(f ;b՗?qòԜlv:Dm!K q`nA &gb/,G&S1mu cH1+RK\.7`,A@(F7*W:.vAi'f`玭, DFyҁ]3N]RggLj⻢8z0‘pohcUXoeq}0@_u-ИO2qhjzrsQ:424]5سN?FnL矐H$%dçp3, oJlCؽse\]IBDxw {܋4PXW RLi&#8xr0DA DӢ/-.M [+H4ލ&yEUcʤvH)-'2L[.O=t0 hGJ/!C m$]^afȑCp[:󆕈O9RjONG[(J Ug_AFmӿ0*+ IDAT ΕxɏT?0l_†?StY~u\5!wҤt &a[;Ԏ~xNةblx[ABhEÕ?PhAܰNm䦜R8^U3 Ũ=F&_vc-u.3!@1{/SZeg1I'"uBYEW_7Ҳj6MVjR`(!HA9.R/DD1BЄկV Mi[A ?{ H81jOϟG<2l1z@İjb{Lɪ)Հ 1fFb=J&#C` *TnANeݣ(˂Rj($@_+:[_K#USWR[XJ$:Ry9^'JH8MuEh$fMYADm3M @>RG-erFZsR~pr D~5M b+E9(.5R`f޽zIyodqx2S*TczaY\E 5BH,+qx xvmBk).WwyqΝR vmX] _@ Joim0,!I7?,{}ήno{H.짶Ne h8`8zb/j"x Ky]yt]Օ{ߨɖdIl<[16S3T(̘JLJ/NWEA$MS$T PIA@0e[$Kekw:xYϲl,ybketgsgr҄@$4#ig}~ %ep]<'L$q=;C& )NhKnj>k ,J*"?֮}RzNGI 3tMRmPa}1 VP")![%9d"uۨ@?,O/V[K#lPYUlǝ2sZLO?H)BQqtyΐG)h,?Xlٵo=UxJaM  W^~ -m/ =e TP2ٷјX¬Ge4I):/*rQO(lٲ =}#(]@)5'bs0t-H9BƇ !nxMs7yJM0P>d' JBw_ǮMlVA؏wSk hjjַGǕ8׮R* $6n$CװG8qݦ$PQyNs}eM#>RGa"3S7ND 6(=U_?a !44~ꖮ魭4:4*Bh9خbܩJ_=p׍ jUyM-9"|9 '>cϴv9%J AfthQ$⢜eyEiϞ@UylY$Tj*$e:nDcdl>}}?A* . C|#ݥW?e³=H7z`ۇ~L]K?Oq.P]](E@2X=Јֻ32_/ʫ4@蠧oY_=t%t k9R {삮Z7{<. AKNd+փp QomUo;ko߁Kq:Az#ke=jA/R"w OH < J"hYԉlJ9$t#u&vk}TF> ;g>Cm MRŕW^\WVe DP+ C_p{djOW2u$߼_pI׵{@)]1mb\ӽ:VDbIخsB @eiъIl?b8R'B~뷔.QSUҒ+y_ZMZ8ĦM.bFtNPI >H}~;͙=EɎIe->=*pQ(ϻ vBmԚ iV5.Uչ{tfk6ze헺;'WGA8gUMM;8KZQjB-L*s_B ¸<$q =s}װ ոWUU*zt4%v}mTZ pgʖ~TO; PܙUB;݃qCc;ZQ{m;Jʪ)5*T}pkjgU6hh8ViwC?yЄ iMzW?n[cΗ]4TŷQʼױRۀɗ`'[c2K}vhTTK7ͳRڼy#fO+vVeӎkMo]z)nT|c:g, :^+!p&rďwTwk,^trq*>3ǽBI A eTSsSV-Wcyf.hn@j97bsK@ayj:7bմIkuۿp9[C㞆څ"U:ݦiᩧU>~* s b <΃ߍ%:诮zu@{gT=,^DǼRP;z]#n,n_&UPꂋ.9g! D=fHܻmn8C}b1.r5s:\7Q–!@WOd ;ߞX+i< ݝ!݄577mȺnԄzcKZr:o钼oY m޻n[Kqޝwݕ {MY{!ʧfΜ2DA8C]C<@cS3n:8Pm G^9b sQc>,Xz1,Y`W;o.@YuZݸkX铞ؒZ\zʼ7}"C'O{ftϯsy$mBx-T]Yv"0m y|њ {o"bguϫ.vrIxe9I ӆe$UPlj>?Tk'w571vוOGUVL ǑGM~%&ڻmwZ2Zܵh_؆H•c@!@!0\q͍(+;p1n:}PJ0@0`@״-J=3w4Z9U8:?|I*ܼKP5cINe/Z_Hl~1{J*W|\u#7U[wP4 qSAo0``ܩRӵ;~ώ&lcO;œ6lOƞgP> Vh:v? o';UNl! G } ~` _c|@w}鏠~G+qe56n;U3PRjtExuxݹ]G~/'LX=z:'bܩ[w^cx;~ ]{WM~ȕDBҴaZ1SDɴtq R5N ot憖0Ό>O c6?#qg 躆OǔB,;uhǞ Z},y➯_30l8I2ͣceZ =5ӦNie~WI3;v)v>2썗J Cl3aIt!#K?`uEI9U|\T#R*VJT8HY+cNi9јٸy5J~ϛ0#z´ Ex-J*RGD4]AӄMv'v{+4Me;K uǕ4Z2#)doX%p[2 ۰? 67<U5M+iڵCOZN#emt7+_}w~!ʱ֐oOo.ƒik폵z2x`ibU R"*QJ\)u/H:;`ZΡxj޽/fgXE[Mk_XYn9x,N$lm>qL뒦  N(Ǔ_xmo1F<s߷M˹8Mb SmWSr$iB C-m}pg{zwAxª&4&-d<#Dt#5uZn3:&yޟ>OvIY;&26qQF]8NԈ6E1>2 y} zrF7'0;|GKˋ.:4-8O9Zn W{2|DLGՂC9JՈᣏzC ]sp %{z@xs$#K3uo c-7}&aoxs]GG݌n7NkX^{Bv'H;^qdY{$nnNsF'ҲԲE93m>!fdCs~g"%3$-#א 8o֌gA>{XZإ<7->N;0L /1OkAlgV> 6M384N5چQ/7F KfG9{zy Y9w@+8noŐeR1 5mRlZRxg(~,Ejׅ,ׁg2ccrX{c~:{mɧ}ug^x,yʓ~f;8xs+y֡3:ހ}TgKU!T0D]'Ok׽asAH)fixJ?NX@Q|dioL x,9Eqԅ 3 C- ǵ ۔JjۊQ)|DCL^Bфu9 RD]& ȖWqd1oZ0̧Ozs˦E)lK)ۖhjn&KX]IDc&,YTXEvOKn3eh%WR8ԕRHR~O<G,RgeOS{,@G{;{0g\Ep2 0l`1i+Mn$Pm ㌰olg7ۃ++TMJT9 "9J.<e0 ð}-7=6U]]Id2 k>]3-4(luI@a`*+``p|rᲞ6W 0g1IyiP&'TVLJf>P$:$8_v\v,VQVy AT9>3gƮ]aⲲI%gǕ|{a,X(s]PY555ZU.@sntvH4yagOdΰأ%V) ŋT2"Dχ׼XFuEծ]W\J)(Q VD47>ǕQ^j2M9,w]ah-lMPTΞȟ ?# ^3 0l`1cdQzF=pFx>!AZsHa|@GVgab>z"2 Rs&ߞ˔\<ўaa9:C0,ptsS40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ZuU6IDAT0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 WpIENDB`python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Hardware.rst0000664000175000017500000001752512314530407025461 0ustar oliveroliverHardware compatibility ======================= Video cards ----------- We generally have good experiences with recent NVIDIA or ATI cards. OpenGL mode should work with all drivers that use an OpenGL specification >= 2.0. Drivers implementing an older OpenGL specification (>= 1.4) should work when the 'GL_ARB_texture_non_power_of_two' extension is present. On some some integrated Intel cards syncing to the vertical retrace does not seem to work! Working configurations ~~~~~~~~~~~~~~~~~~~~~~ Here is a list of configurations we observed to work: * Nvidia GTX 650 (Linux-x86; NVIDIA driver 310.14) * Nvidia Quadro NVS 290 (Linux-x86; NVIDIA driver 295.40) * Nvidia Quadro NVS 290 (Windows XP SP3; NVIDIA driver) *We recommend to always use the Expyriment test suite to check the performance of your specific configuration!* External devices ---------------- Besides standard serial and parallel port communication, there is special support for: * `Event button box`_ * `Streaming button box`_ * `Trigger input`_ * `Marker output`_ * `Cedrus response devices`_ Event button box ~~~~~~~~~~~~~~~~ An event button box is a simple device which sends out values (bytes) whenever a button is pressed (or released). Event button boxes can be used by initializing an :doc:`expyriment.io.EventButtonBox` object:: bb = expyriment.io.EventButtonBox(expyriment.io.SerialPort("COM1")) key, rt = bb.wait() # Wait for any value Streaming button box ~~~~~~~~~~~~~~~~~~~~ A streaming button box constantly sends out a certain baseline value (e.g. 0) in predefined intervals (e.g. each 1 ms). Button press (or release) events (if present) are added to the baseline. Streaming button boxes can be used by initializing an :doc:`expyriment.io.StreamingButtonBox` object:: bb = expyriment.io.StreamingButtonBox(expyriment.io.SerialPort("COM1"), baseline=128) key, rt = bb.wait() # Wait for any value other than 128 This allows for instance for calculating the response timing without relying on the computers internal clock, but by "counting" the incoming bytes from the button box:: bb = expyriment.io.StreamingButtonBox( expyriment.io.SerialPort("COM1"), baseline=128) bb.clear() exp.clock.wait(1000) rt = bb.interface.read_input().index(129) # Get reaction time by counting # input events since last clear It is important to notice that operating systems only buffer a certain amount of bytes (usually 4096). To prevent an overflow of this buffer, the button box has to be checked regularly. Additionally, an ``input_history`` can be used on the :doc:`expyriment.io.SerialPort` object which is automatically updated whenever the serial port is polled or cleared. By setting the ``os_buffer_size`` correctly, a warning will be logged whenever the amount of bytes in the OS serial port buffer reaches maximum capacity. **The important part now is to update the input_history regularly**. To gain maximal control, this should be done manually at Sending to external deviceappropriate places in the code. However, Expyriment provides also the possibility to register a callback function which will be called regularly during all waiting methods in the library. By registering the ``check()`` method of the streaming button box, the ``input_history`` will be updated fairly regular, which should suffice for most use cases:: expyriment.control.register_wait_callback_function(bb.check) bb.interface.input_history.check_value(129) # Check if 129 was # received at any time # RT by counting elements in input history start = bb.interface.input_history.get_size() - 1 exp.clock.wait(1000) rt = bb.interface.input_nput_history.check_value(129, search_start_position=start) - start Trigger input ~~~~~~~~~~~~~ Expyriment can wait for triggers from external devices, like for instance an MR scanner. When updated regularly, Expyriment can also keep track of the amount of triggers that have been received. Importantly, this has to be done manually Trigger inputs can be used by initializing an :doc:`expyriment.io.TriggerInput` object. **Basic usage** In most of the cases, a researcher knows when a trigger is to be expected and he can wait for it explicitly. Code execution will be blocked until the trigger is received:: trigger = exyriment.io.TriggerInput(expyriment.io.SerialPort("COM1")) trigger.wait(1) # Wait for code 1 **Advanced usage** In some cases, code blocking might not be a solution, since a trial has to continue while waiting for the trigger. For instance, in an fMRI study, a trial might consist of several components and span several TR. One way to solve this would be logging constantly all input events in a separate thread. However, this will introduce timing uncertainties, since the operating system is in charge of how and when threads communicate. We thus decided against an implementation with threads for the same reasons Expyriment does not implement a main event loop: Maximal control by the user. Nevertheless, input events can still be buffered without introducing timing uncertainties, given the following two conditions: 1. Incoming events are streaming, either by sending some baseline in regular intervals (e.g. a 0 each millisecond), or by a regular incoming signal of interest (e.g. a constant TR from the MR scanner). 2. The input device is polled regularly, such that the serial port OS buffer does not overflow. (Most implementations use an OS buffer of 4096 bytes). If those two conditions are met, an ``input_history`` can be used on the :doc:`expyriment.io.SerialPort` object which is automatically updated whenever the serial port is polled or cleared. By setting the ``os_buffer_size`` correctly, a warning will be logged whenever the amount of bytes in the OS serial port buffer reaches maximum capacity. **The important part now is to update the input_history regularly**. To gain maximal control, this should be done manually at appropriate places in the code. However, Expyriment provides also the possibility to register a callback function which will be called regularly during all waiting methods in the library. By registering the ``get_trigger()`` method of the input trigger, the ``input_history`` will be updated fairly regular, which should suffice for most use cases:: trigger = exyriment.io.TriggerInput(expyriment.io.SerialPort(external"COM1", input_history=True, os_buffer_size=3000)) expyriment.control.register_wait_callback_function(trigger.get_triggers) print trigger.trigger_count Marker output ~~~~~~~~~~~~~ Expyriment can send markers to external devices, like for instance EEG computers. Marker outputs can be used by creating an :doc:`expyriment.io.MarkerOutput` object. **Basic usage** Sending out markers is straight forward. Some devices (e.g. EEG systems) expect a 0 to be send after the code. We can specify this by telling the output marker at what duration this 0 is supposed to be sent:: marker = expyriment.io.MarkerOutput(expyriment.io.SerialPort("COM1"), duration=20) marker.send(1) # Send code 1 Cedrus response devices ~~~~~~~~~~~~~~~~~~~~~~~ Expyriment comes with a high-level wrapper for Cedrus response devices expyriment.io.extras.CedrusResponseDevice_, which allows you to easily use all Cedrus response devices. To use these devices, however, the third-party Python package pyxid_ needs to be installed on the system. **Installing pyxid** * Download_ pyxid * Install as described here_. .. _pyxid: https://github.com/cedrus-opensource/pyxid .. _Download: https://github.com/cedrus-opensource/pyxid/zipball/master .. _here: http://docs.python.org/install/index.html#the-new-standard-distutils .. FIXME: io.extas.CedrusResponseDevice is not in docu yet python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Advanced.rst0000664000175000017500000000054112304063646025424 0ustar oliveroliverAdvanced ======== .. toctree:: :titlesonly: :maxdepth: 2 API reference tool Test suite Command line interface Data preprocessing Importing data into R Using non-English characters Plugin system (extras) python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/expyriment_structure.png0000664000175000017500000014507112304063646030227 0ustar oliveroliverPNG  IHDR6sRGBbKGD pHYs  tIME !S u IDATxwxTU?i镒@* $4е7eWw-uߺ* Ql MER @@Hv=?f2@T$9Csf;BP( BP( BP(3c  B7FGe,NM])FTVU(A/X/kf5uӔU O#cfÒ]no&!* ῇ-TIC?{sc T=ʛ -Ή/ш~)R + EB\ܒBXr/oƫ2 BwOqo^ BBP4 X^f{,!Mt(vP9CLXɑ}5M#sBh$XR22FvKsA7MSڕ QEB`IT`"\ЅPBP4:$ Z+ksARi BшjP( B BP(2B&U@ݎ>!D_B"tq7!XOW_^yδB]P7'ȪsEݤOfg|&!R\`WJyʔٚU#UH:}EwKJgr'O~-u!)%SS+R*&]=P-h{)-xN[Zz++ ӄ B BqZH)q:Y61wLbxVٻ7"(>E۱cIɝ{Sc4>޳H)Ѻ5my3DEqQ۶?xhC\RJ;v!ݺaTNhƑ>ax&>ДWb~)Sy}hғp:Xy;v^s>|y /\@ϦMY0f >KJRpu : ֯@$4xm^ eW^IMi2u* OMeY~~(~}h}>n3_~9㙷cwX8Ps\FAEuϽ{޽L^E{bݺ`ff Ҧڢ"JZ6u͛y/Oߖqp8TT6bk;ș(*v;? l=}ˆFE5M.Z4O HTPl^Zbۗ. fcERQ'Є ݛ0[ u׊xMMnwx}bNZFE!4?h$#"mZ ڵ9AnٓG :æGy # JǿcG ;t,m1jq#ێw #FpkOfK-Zp^؂ϧP(7*;|uaFWpOlJz>ˢ믇8bTsB].w:0h쐸=ҿ?Wv`16o߲( E:.#5/Y6`7t%%93kf^9QmڠɪC8 L۴ qqL?< Zq-*eX~啘@ʫm\֡ێcVV Ad4`CW/2j#P(J&0X\ Cp:d޽d&'3MCϞ<סإ II-!ji!oFA"1sҥXRֶm NIay~>~?s#v%&RzB^MxgLpB4kiDGCPd\guzuhlډq* #nYBN\PP`k;|Sg%%RJnҥh[UޫEJ[4Rb'{Ŵl 0.p^-9?zz;=!-l :<'1n ^P(AW(~vbˮL0aڏ>u{Vv^o^Aq3 sӉirQ۶{vmH8/HKkD?jy]NGqu,<<]}@ffhҞxLwNMR(/jRWgxVߏ2͚ŸEX[T&vMUyA>gNu(/'X[TJ_ض-2 MTG^^u\g :U61S|</m؀߲\`^}ʤ!FߩyEB BǰT˙q#Υ}HFefpy͹{wFn 2flڄvj?eҥ\D9gZ8 KM%ڧm~?.37o憅 .,dMQI?ji\ȝ˖1fJ^5P(AW(~8~?SG %8[y";ȑ:H)tfw>ѣMõĭS|<]csk]]Tk\ܴ,.m>t/?;xJ5VD:غښѮy8UTsyع36Mcѣmjۇ&1K}N|"22e?sBߨe"aVVLXl6-2i8*|>th4kvJ$_TweGI vMcرט&LRfG׈p!0TPĺ\4 aPL: yU~?U NW?j}rMGH6okl[SCk'a ixxȹ&هqM%!!Ӎ2gjR7&EDpivNjs.$֘ V= p-iuƆfH]Ɏ nv8j!uu'p1 n^#PBآlUG. [>_ND|]8hsB B=(Z55k8Ji?w.{KKkm>˙b'B2' EZO,) %SloB"t7dc8%٫ET0ծUB]z3LЅMiRc|TBh~j/ÏJ aUY!KP( [ PQ]> %&٤BP4*$5깠sf[[© + E%*Bk;Yn~IhmԋnHB`B BcWiN*\Ы 9VEM4y~4"L-S(&`kpĒ9/g=zV>VZ/m>΀CUP(V/rfa-EeŐq BH4TAP( BP( BP( BP( gV k.Kmbc}2BIǏ^\J7.Ƒ>)!GZR&Ӹ7t! ̟?M}΂E2M1W@Mr`/'kwkbv}3cH)EW &!$+}Y"z_Aw,r Ǖ>al'L]]_+%M; WpO.)04A!f)%ς^%:3&13Qkm9b)t!f=QӞ>q(:F>)Y\ 5.9ۿfƖS Kb{4` ;phb/'Ky{vͶS)%,lg⇒elc)kO9q~T e3@qFvMl͞M7p-Fa6 A='N0୷T\ҧr8u;/ڏ?&7;  c2\zK׹k .x=J ήTU'ܼx1NU&N]ǡ,/`EzЋi.]?#p;ץĻ\ Et *k4kuݻ>x6s: k݁l6:%$f! Ԑm- W C޷Um]'NFE/=Y&$..hĚg[C[]5ZT# H}\\hCk=))!őqMV-*bĻ3Y{W_MUR'T %ܺu MMaY6!x2;aH>iYV?mXf0#da6MRYfmBNa!n_ͥqYg1#p:fu ++yt*VT5!G @O>kpmN|{w(̤7ޠg׉,ܳKJ| >(*~n^O^s0oltk҄W6n䍭[CY3!hWvȤnx2;V:4H2 8P!-Zĥ}5ck]i/ BWZΎ/ X("%ߴ={0'駤DEppܹ,عQKjBwfÜӹ} FjD*(={u:6gr2M)(/׶u:C~ׯNXo]go_}ŭ}F~̞bʃ;t>PdݎfW,r͞;2%'KejCEFe};С8gwi)|q tDzRv;wXΒ}>J<>ٻq]q`'#`Ν$ED0s(Rr $\<5hogѣO}F`cwbp˖,2h մ)O Há~ʴMyr hEWZzAio!F/BPicڵcQ|yuflU:5VK-+c<1p 7wƲXo`&m-*ET;Rh:'$P~Lek-*Pe%3G6mƎ&36o¶mא!XRʕ̽bnٓwC\WEܛ|̐&~bWc#|ux rmő#B&mc{z<|g}1KHJ<޽bbck/]J /P׺iYlx a,_64q8NCe>]qQsog§v4Lˢ0p:>D!))Ĺ'ݟcNIE94k'#"8P^K.>_Bڵ4u:4q8cbR,P,2*|h@fR;&Naު)<nӆ c20չbwi)|ݱ9;|C׳aXNL$САA#VTuҁ9Sv 6o&nǔbՁjtHUOdg@Fǎ~(vIջt57mRڼ8|CYB _ZMvp1۟*Gp[u.iߞ=l=v Ν849)Q4<~\4ߵ+Y6 Šphʟ/̙VVr~6$\99۱ݥ%>, e,<6`3hllɚ"*i5Z"dYYYaWI %e`:Ϙ+VHJ0V35Ӳ];Y!sSݡVh"ֆ4q˂1c` S<"iLxM3['13;܆ZFEa4&|)~8Zĩ<>p ϛGK/hnܙoJTɜN4е+""uܰp!iӦ11ܟA xjOKʐS9pl6`;B`JWX9oMFa0s(>T[(7X~ykܮGwӻysFqUi!(zA5!i< r iCfR~?^/)|SR\RӫY3#Vq8,ƮN~Dj~IezτzdsO0z NI!215E{ECNaE<"Oae%)؄#ᩩx&QQӁɑĴ,R~̀9fq~+532kՍh!ѿX旕a4##,{ Բe1ܔ,ɚy$`]Q|MhcǸcGNӈfZiX}Y#cKN"n0 IDAT]ňVB׫ Mߓj95mtTXR NK)Iz sRRQg~ ;I)I #!;t2KJH L )%àm\mBb`Hɰ_PͶjX""Bl3VlYӅU)%N-4i`[ yY X5FvSUr*{ctcmQ^LhqJN>w?Nh(69lFφzRN;CP~bHIIOJ QqT B z`$TY7з_B͏[&!Kٯn%~\"C8uDhB9f߆k_~ļn/)d#~uK $j{}tK[RrTD"w*$qHvMwLQ/6;V>(84)k+Є[ȣ~4E}bI84%RMܹӏV_??1Jy/T;_#~"XYG-(1ԠֶGPi!o &{sgoGcA}dmB({U`}^Yך˞yJYhT6o@)oLa,vV~-;_g{d=]Rޝoa5L}RzWl o7DZ=BIh pE`5.BG4MJܬ 2&^!4 G "yNȐrD!ڗ;cCb;Y;bulnN몤Fx-.+^2j~7`{lBψFm;UR!,ܬ g*gO 4qvHd[=/J)wx^E!+6|Դ zM IkaB >•>OnV"o/b*v_.\jBې9>|5zp~_WB4E 6A.X͙{ s17$NBnCymξ8=c",.6\S5{|"ȜPqOTR[3xDa BP(F@F1 E} B *~GĶH.pJ<93wK՚W2oNwB_˒"EO C .I'=/;y;Zu8SK6"}D!NhܬBW$<f1E$ uv4gO/xw5lb߉62QPI h_H7gzd~w , r {c.%X1g^I +N_"MkFg]Чorr59%Mnsˍ<²@"%blټ>&u? fБ:V,Б>!x3x~GŲ@J/ٕ}ʴgNƟ=HV<3s6C&5T!RD>HKb^IJLtڢi7JX b7VϸVf?OL49Ahi~_߬\J˥[%|D~뿾w},lQ| {Ď_.iڑrU12-)^`7{cv`r2a$[n>>C-RJ\a:Nnn1)d}~.}<ܙ2'^o\??:!wkݼ 7XtN_] @Н}'Ҳ0 )s/Q-&|Lnϛj=k6ؑlo_6%%xH˾ܙwfL› OO_C:y<|_˪={ĈNtUJChRe}ZW$nB7+FtRPES )p_N2U<&ؒz\&}wSLqyM==#\djoUunkfX6KÐ|RJvM..cO3} y [8_ۅ4(\YViܙ r+˫?<.60Mlf7:u3 ƕ1qi9=.'@JI[ʷl}uhLg?z M rvL4(BbqZMMGBsYmhN"&V_'eqf TgyRa12 jg;h"FgƄ6 SH-)ɎF>p*C'@: #a.z㉛eNj9^27]=RƚH]홓Z꺈k!-KDt2] Ðq!jm_2_>֭K6ibk^P~"^GZ d!ͅ..qlPS7>)Ku˛%Cڧ$&+֩D?+fr_0ecK왓"5깠KK: p\X؝Z?'z]Y:{D5(t)RSΦ>>}ZYYeO?ݻի)e/^ƍ馛p\ǟa3aL?4@dD蚮5}}<{,.;:{~ o=4nZunE7 e/7ќ?~qM)+`旅\rpJ;5+.aR Efr㚿\% cŻ(.]/wn#gFz LtWhvCN9%}sVAx̟C^ZF<310+y8\6֯'X:{5z1fܮU?5IRd= A@Yn`锞 ɫg}m:__$63>G5c.\=v̴)>ph,ЁD56ɠ sСWk6~}[ ti"]x""8~W.^ɲK Nw޽nnv:)++2g-ZeϞ=eɌ?"4MCuJKKx. w1?>z)I]2,¢\̒^uZ `l:nMei1} z(;tkr!飺1t k8\v,/#ctwzʹw=kՙ xi_ަ׈L|rv-F.}˴ؖ8ңG?7S cڴiL0͆i۷pm1uT>lٺu+>,3g .K/m#DЅVI)wݦ gaJJoˁ).8acݲ?@ޭ)/SzS&1uw%a<~p Mcwk7ܕ>N>*/媻'!]ӏ%oSzZS( s:@aQ$C]2L,)9aR;&cMz 5l%u]׾@U{gL2L]iXx>C6 ^;`wpb&O'>Kxz4's߫74~K$ skXgNIx+6RJ5k@ǎyCa'%%ѬY3HMM[nusɒ%|̜9":,LӤLmrn8p7v ǹV?e qMLWObX2.A]xX:{54S4 : t}=i"ԎI uDĄ?0?u(JO DE^pIڒsH?UT{Mk<>N>xi>Y,ބ7p=2~X=#_/]&4 = Sە A։o?DazVR:&Qޭ1 GגN>|i91QmiR/h׳ui'.$$kosтc{F?˴L"m3ZkeYgh~z*|k1X5]pLv*wsCcr]3x[[kﻈ4km^e=#b=a0{6dߖɚnkȘp4]]V̟n2a|az*cnAlhy:i rESgæk4oC }7 !Dhct|d(#%Tn!1@upNBcA4llvXN'iҪU+~׿8`ٲeDGGFdd$.ntСCcxlڴW_}Wt$'68-S{D4]4Z$%k_4Qk<ΰK9PJIBR)ӤfczŴiB\qAo6vm۶aYO>$~ƅ^[_}Ulnw(W)~5Amzp}HojxZ'Zfw{FSik>g)C^kRuqJY=N^2uP(W,dADLX^\-O\62۬nE9+.Vu7j5 om\=}/u/_7`ʟŻgH)moty-hT_Su]QYZU; \Snow)]ڙ̞lr I8\vrhC4-rbQOJv2/Z_ :@[W~r o.I;;[}-<OB-F-kch1ymvW}ϛOs>f?5GeYcWM×LK4]c?> X*4-i˩*s!&MSsٌ?-fj?);^OkYd3Y㭧>b Jؖ;'{w]Y4 8IoQ;rn[5\ř/C7&K,/Id=8y-f#gc~poniC΢?g v}'b'`J*C+o?)K8M!"x0ťTO!RYZn{QqǔI=+NBFծ'g( pq;q9 ,:G"!2j ]ai5,:_@J3́+ƕb::M-Aކ^ϘGkDҺlv6S|;ukyv4h\J)))v뭷yf+~6~1tMׂh3h: T\opJ]~?'X}䆿^u;l}:n1Θ⿤goY`_1j9|r2Gw]a;9h׌ƾMO]Kh|㪻GFOTsXg4^GXtO}UwOx4].DƄcY/gC~]ڇ[3wNczDņ3uxҴ8w@:g c^XV}+cLn}:୿Dxcg)<o07gf{z% BI轄.H7iJ(6PADTT: "HGIwBBz23&K*R}<ٝs{{~.I. IDAT{@ ^-F F=zĘb!HMt*8)ocATv#FLpp0&Mȑ#L8 /ng޼y|!ػw/ϟ'33ƃokF6y1kN>$AfJ6f#CŖe?g&.^BTEeǪ,V$ZΙGiإ&Tzv5D"MXKrd EqlBQ)Y(Va|} odY lXk92o /|ދ~<[u&f0h\JU/JdMər Ưfe^jLcu,ht&kw)ίdا=Ҥqr^} @nSHKĚepP0Zس./)0_ E0tRB֕PM{|,+Wüy_>B1b7ofС̛7z `ҥTRaÆ-t7|  2R;A~|v$Y{^&|"9Ffټh:T{,nNRšRie6&=") H2ٿ񄫊bwJ> E\^"s_N59Ƕ&l!2 IXXd]H?ć׾=y:KRR\?󞻫QBK-y'eW!twCkpP&=Z=M`0^~u ˹=<;c;vd-\}" 2Ke vҴg|KIX7gQU37#4A@:Tsdn?C%CaTyۼ(i;+9;v*nt$-pSnB,Pȭ\ɔ#8,dfz`COl:ȴYz)S/ f[šitmfg4g^QOvm*^iX3mdb9L͢Q [o P02 $ kUXˤ'e͈<<̴D7OE_} vNnL64pP׃S$0$6n˗JHROq_Cd awٖl  :ǯd[ []?"R>yvo}7__7OUӔQ_m?C`?Lك5uG~O_<ޠIk j3xDyXm9Ft6Tqi$5Wnos3= ѾgfuKtg rx$$$IH,-g/{Kyf㉉!::UVѭ[7|I[/^LzzEJJ $sqڵ)SмysuƜ9s>|8|򉛥x@='-薁'8gړʯ7QNdΚk:1Sgkdgw%+'c%I:WN~ptÚ"=pO_#nNѲ")YL-^~z~]i4^ Y*OȖmgֻKP/l£B ڂf}Qz1n:0;@/ғ2 ) g=p:ݏ=/>'m4k ???_NXX={dȲLQlXj{f͚5L>sYvލ/^}KI䪢ѬGWMsHQho7E{9 ?!c4Siy,^ز["![[VSla'>XNPBY1mQU1orV}'vwf;uOc=:KLQNˬn;IdeUW aWxSunM'B3o5ӆî8#sm/3>^x{i U'eMd|x^HF^uQ-ˆ:TmbW6ԋQU ŮnHGJFWUT^7'{O}'/M틇1K_bBC'uG$B"\ o6.'bUАka4䍇4$ +Mā3I=P*EJiO_ U|sTj388%Kp88s T\?p% BdثW/+WF#k֬ZjhтPvs!t7m;‘#8T;}3nu*T犜8w;+=J$I8 _hF;dK^qt65uMhry>y㊔.ld$P_>Xc&tÑSG^*ciu)X[^k_}K`0<Ϩ" dk IrR$!HP!?u-ێDµ$ dg x4˨*y4UܓR$I-ttY'sO/sOI#q웁FN5 _ցNE 4!$gE"dc===޽_nv~គ$A.zwAZ VAQT.My\>q qH-k\qGf=M VyZb&g^v|G, juj{ʳ߶lweW8]N_Ocxl3+bnîpf o,|Q'I!([HY9m#&#=a8i w[gx9w2fK_d瀾_tlm>жbWQW&ΨE/ \;ިÚe#+Êt˝iv=Mħ紅jU**ZN%5YOjB:朌 4a(~yWedgX1%eߒ5tجvRҸxڿ)_SI+JŤj)%wH?)F%St=3W^w%xewI~H97FĶNfYwK.6T-uSSY:JGOA(QN9$/}I'|#Օ=kC *v¡|xĸT Q[JU/Ùm #Ce[L&3-<7+6b{훋(2{>U$bdEBSU}+ߓ끷'z5O}uBx@O_îE"+D"-kx$*A*}~ԅ_J@.KjB דO~''L[dzݞ-[*,^rOÈFM$^O!z6nS\TiG?C&۾g^F+ΉM8 `6R~Il;4!6ޠCh29 \}̀\2-qc8kw怈{9/׮]ct֍|9Bl6O?4۷o`wV7$ \,{tE7`]^{#% 234MlYbVIQ8*1K_v(R /IPU065SnIv+^nuɯobn~hrdQ*O{Ink@%TE{T8CPMJ ᨊFn o<3: `4Q8*o36ala'{uJ׍nsPNTl0?+ZLyܼDP!?p ز\9}Jo ݫ~GnCt(___MʟHb, \(TFLJ֭[pPUЪU+È#1Gnܟ>jK(/kjvIr&k&'nh0 #*8tXP2ew0;g̻l.~j{$Q$Kw$j]3qW^x*y$Y.7/y?ɽ$󂨪1q{f]dX׼36) $9 4UOퟙpF#¡q3U{±w톚UEp {<9Vڃg'~({ge) deڵDZ1W\z+rh{^h n>[5pYy)Ϋ/Ԓ}%@ˤPU`WAsR,VM=g$UhH^FΟLHAo zI3cq`1pL2~F|}hlA@ z՛/aS:t2g%xdt[n<ԄiVH-zkA$<}-wJ8H0 /ؑfVSUeOPpuB GW^f,&=a)eKs\7ug[IK\ߺ#%~O; ۳zbnfҷcIjFlE6^yBN@ 3A7иVAu;cGLFM%z- aneݬV-p?%Mx>G"/L khęwƺo?MF9"2^o8bR|:d6  ]oY㚠nj_ObÂLX3[Osf%O~gMf`Ug9b) ?G.&!&P%'ٶd/YVJW/NNՉȯm'$"җ}8WƑMBL2 g9xzabR s(R&!b >^Ԑm&QC5.F(iֳ.g_$=9'w)MIsi7/#q| ~F^ų]˰z:,λ3ؾ?u Db6fԪ\yҨfA\Jԅ6*K}*0F9DM8M8eٰ##jڄ];&t7ݳ IQݫO/K zt]f{ ثI30ܺkvoA#%sJTjX}SVKp\iIy= 9/ 0s/Ӹ{-3lX-?)}*Fn[Q גXv~,IT_}tzq_g`2w1F-zQϸ>_@ahYvʫ_#T(NGqL+&$=)w;~NM(@מ[m'3h:V JRHF"2x6U٤FF>WeiR8T#̙Јmb~šM,EJ3Z\ˤd1?LFX*Uԩ¬NHLazw.EEAwnn{n.Цj{")rN$GRfRAtĹ):iI!Z|~B{g2c\8z8՛XB F҃=-&΂X ?WNݠ|(b/%c|56HZ,S7phI*7,/MB reT Ur$N'iβ4rʄMdY`p6CG)sH 9"l!ՑQl!V(կ_xRht4\iѧ$8l zÚ8TC|9̨8 Uc˒y=i de<1PJ|WԫWSN! 44y ݍSN13HRW%!lBz\]dBHHvX,S[k({Ʊ= *7-e!}jDNMg%p6 t vHT$YbΨL Ih >62%ؾl>NaMMզ@8vaWt4QihF%[i97+Мtz91JJ1}I\`0b 4i>}RɍTPEnA/Is$iR_YrhddqBt4o:SEyB"hحNA*5*CB\BgahO.wbpG濜@ !@'qD2gxNOoǁXSH˰(;őq0bO̦u(ծr31cg8{)l2.iEV$Z_0$. P_69){iIܸpP?b.đPu\+.NPIhB`˴(N^S5n^K$X%q5*/H$LFbSٺd/fPC1TF}TC|HML'M5Nv [K TlPŮJPBJ|:LGwQZxyf˴:\,LO@Q '7d‚=@ol|m9a%ZIppd",1bP%l93;ڭ KeSP/XB?4'Dz}w=W"yژn;[LD}( K2x{`0&%aE .)v;LΞGH᭧;#0 vRp ?،+SN$[e ԃ͋`ͲczݞjqG3 qͩbF[NݘH{?')6e_n`?rEt:]e|oe9Ϭw~BUU>?VݘZ};d'e;[|ə!XW^yII#;gcR3lAORl*,ҩdgr/ċJeټ;+pt$q=.:UBP2W?1(A]ߘn] 7 Wg2ڟ5ϕĭBR;gm%$!},s-)ҭZ"v/ީ,(酖{zztx ;, ΰb0ن|î p e[o3z{wMtˊp$_77.k?2jrx{1B2 %z2DQf .FHM@0{8I/LMP_@OmAhvY6 !^{(*!o)IDh7d`0:sN]{b T/L;gǵUQEkØ2'vdԱy^[>{a9 .Ih= Af$ fm@E7OR,ܛ5@h& q| mR# CxO 7ܸτn,F?VVn\6{獉iD8sf6y6N ~K;作,,z ]GsӨ_#7BӭM$K_LqFbeNڕf3&/2ZgT)D|4?kԮ7&(Q)5ҢO=F߼:2ݟhk\WfďToQşešxx[TbI7>2XK[Ezr&5@~dҨ,9p;\{2|E3l N(F`A~_ o$ C|p*!Adfw9A-; [,8T3m Iw=(@Kh4vŬ̜`ƿY 4FdQdYh8% Ҡ fѮAA@D&HI}ZjQIp$tdAɢ,2wBCdYd'%Ɯ )ϵ!X"'w߯<˵#ǹ-=HDYleD3a\"}(RȋmظcllW oa>ȓ Tc.wfSu9OVT^\6BHJ|:QURQY궫Oh1PZ16Z&!>ef(jEY0~5, 8.%0$ǧVKhPڸ,cMe8{&r:pT yDx0U(̢Ikiڣ6~;bF:U\!z i? :ȲD$. ArFݧ&JBL2ۖ`gˉ9p#[O.%PH}m5:jEjW ~pDfNJ\[Jpy7-k嶴sm^hݪ"p_&?miU%˪Р*v(m26B @ XTɨ#6!>NyHJ^2AjXt:֚!v-ݎkcػ<^F嬩CO"d',? Bز UQVTFsVȲB0sk= Ѳ_ `4Y9]yl]Λs%;Fn5$ Ůz@C4Uٿ8$0̟9_Ax![kNaWiOv=B L@u!35 Kql,}MWٱ$J"B4JCh87rfO8 Nʷ_qB85M%Iu"`pÍН|GNÊiڼEḳ{Ȼc2ɨ##Ajl:'p5 YV~RNCQ5'0J%k72 a)mƙ[joS~oX6lv|{Nm5ξ}M7]!'ժeQjFuۮeqpH`ٯع4gwEB"5ί_R@ iBOfXjU*@P/ض/*e(H#IUQ3`(4‚ɍ E&!.,N~+^hOFU,Ϯ* A@an!7uhl]ǮRnUCSU92oY|:UBHMs-6" XLhDޫ&\2faӼT鰔#jRbNJv?1}xj;~lGɢ~NP%o,D6,sۀz_iPIĔ``$vv=bp8s+Ooԣˮ-r,~9_:գL+߼oX;??0y"@Ǯ fǮu2ߏ[b|hؾ/?kBJjNT/SE zq\2)OdU*9jNBa4Ukl1v!G\֓A⓬>ߺY=ƃl#zVbv@/Mh1#5>o-&z2=l_fZIәs.qx)<|-df̨\ɲ)MD4^ա>RmaĬg0ی9`)ܼȑm)_7ТpJOFױq&=]ƅx纶$Lh6ga.HJE8s~>~c4r` *>Q QN9,#v_Ӆ-cxm{е3KtTz ~gꖞMY͠.QDj QvVS4fr}kO~rƠJP=`ޓ%NO!)H ^֏?'2yHO ]SǮQuezlKEط(YTi\~;Q\ab~JOF7۰=d$e>as`v8 Ն[* \CCVȰj !(+wG`M$`'Y;Тs\˾_Kmِq~>q5uKp{I>P' /'퉫h9>Cz 5 s6E4Ј{S_kVhѥq]FcUǥ*:HKbT*DXm: ?"ܡYu 8 g!K30uɀ@巘  >.#Dsr5mwKb349X& 6WDt} }z0ꆶxYs>`Bx.m[-?֭{ s'^ z0g:_3&Am^1753[⾍K L1/LʣJz| {*,"qD@`l:QcPG&?3vP땬Wz1um^L@Dl8Z[3~.7I3_k'/[zwUw"bso$go~Bh}'8u"g_>[ҬS#V-@".Hk!c;M;4>Be::Q~G?liGW; )薔#%^IN B ӡU=|~I7Hfh5n+ӚꇺB7ʮb5 O+HB,&{ŭW9OPSGVv`z:{ Ef0~=qTv*en|l-[tߩضf7#NLƄ#/2-Li|:vbt,Ӣݰ n ?2<7_w+%0EPEq1c@2\'ۯo =?,GT)öY~ fܐ$fƃ ߄/ڱ c?{\EMza[,ӢE8BWOoveYԉ Mhݭ)'^nGBHxUuVe)bshuNw+IXDB\4h"OBQC"tKҵ]CX@b#3LqoocDŽtM}#:iqå-oQIu&z>|O ZXڲ$Z֯Gm?_QEpGW9S(5Yb?HcnPyMҵm*WQ_ݮ镂YYG=B|1|y ZRʩ)#(:SܭC${+{JصG\B~ 5zQ(AW(g'%gH+EqE7%WlJl`ń==j6$}71 %NxʽH)G5voگM=a؞( 6Qy C|2 /uutM BBlhZ_X- $T+R(AWvNYX/ sz8sdMulNy7И3aRwBzQ; ƀY!+c(+H9+mn\u JyhlZeIЎݛ qrK7b-./`g'14VޞbVT T~5%WHS %KU8v vǯW'& FNZKX]ʗ, 驻'FPP(^ix3RWM7+Q;#CB^P]4ٛ'3<驷w^9B BxRSq&Ǥ܇4%^_,gcLM.DQ#0 3>wF4{ Z8z5~Z#&2cENC^7jz5{C]PT) ygr}\+Wv$#f`%&c8{w~C]PTC=IqCOfʮYau/@q.[6IơZJi懵S_B BP(rW(jќl C6D4ҥQxkR2>[B/J 8{&83{BCBW"p^{BΒUe'#U9Im"B5,WX(AW(ymX~ho(^^G:`+'^rG~.9G)Sj+4rۄm1n墳_ BQS71zwaBǘoߺ?ЅIY]J).Hȕq38;oڄ}aj/>F8 P(5E`6ׯgkZ`h,tc//کFŹ#5?Ҳ.PPTEFFݎg hBmL BQG rcwG:ܩSy:-wel8u)%. 0ؚϣchum6t!P5` M~HCW(TmcSTI˺uG +)M  //oY<0`7nL꯿oڕVܿp!-ù{w>۴x^=Χ]ْLP"; y`BvޝBiBz:/ffb~4 agQo\^Hpnna0y:ׯnbֶm;ۨ.X#(zOR EF((Hp޿"ݛ KO禶mqu1M2S'&Y΢"Y;tA4EGzVQ.Mx$1:1*AW( DEQ,ܦIhZ;tah޾-ZD:FF-.&❕+iUws/\ײhµ[yiSYU]ѨBqJѮߏӲHkӧUZ+6^ܲMz]DWlh&tFR _jA{;ukl *P( ¬Jq*BWTWNv͚J[,BH)&c&rj֭ __ms'-e^Ti;!ݰ Ԫc |>H0+jQ뺪ɜu$$[l9{UVo~#}RR 'K)ϲ(][#*b:"Z%6]'(|5{ :>n~?B%y`X>a-]G_׼-րm0sf~*#jO~W+c^w?ȎS״:2\zp5Zf!ALzZV.q 8y7_eۗZiEP`{d(X\Y_[V1CgEs,zL,K*QK- Cy:֊Mwn})۔ENm3)ϯ j;rs~!5J@;q[#5Dϛ0ٓ#k}XJ/LY`5Đ\Ӟܳ޽Z/LRJ4M0^,Y!s$$Wy?!|X~0$%r|DqjGǓjn~oyB{m9!;'OJ,;#eAZ{4Kau73efL.#x=Wem ֬q_N|y'=Fxm^z4 ogB7-8GǓ’rr |q8߀9lڵ^GZMKv1vYHNᔧOUC~pk6 ) ;#f IOj~i6"PJڳKJ@MYyMOy|PmxSSLSx\QTdږ~Rsɓa{KS7OKgO)mxlMivQ!3_AJN`Ѥ*q]q'ťj {\tǻl4-xgzW'ٓW v>ĆY,ú'Պ{/,Ң <a)ֆk^ܻײeG!BןUcl'̔墘;L FSDIwN+[|)' }=! Oz5g;vxj"/Mr|3GZRIOyDtO #t?kN{bޏ!jE/`y~s+vrּ9]:፳#`Ŭ۶+t?J笞/t߶Ays|~F2+}7>&rftX6ӛZk|緈d|p>y->u\ i+N{6܄~bQF6[Ϝ_6NÑ<3[ނ; 745E "߸ޒWH7Ol\MKMs'1?[|L"-he~4}!!д@d y…%"Yz_okl}? I)5jdkWl!h5处}{ֺEv@ /#+!?X] #i..Ցq1؍ZY[޼Mep|W㓺M =etnՈ-ѬaU=S;m{Ŷ=0-9M1ofꖚ po讦UnnwusxG6n+ a c"++A.rrx/6ChݬoMY+|U뢀WX7Vq^xnq:\^3݅.ZomJ`AX6ry^YrR˘]L0:dGi~AH͆DVӽGY^.EIɞ=>l`Yl2lb<=yR{hMOGemzOWi&HBk0Rq@f?z̜P'`OfN잝8bę?_hf=$w }!3'eJoF ѓtÎ}7eOiXV=YMW׵%_LHͫk6Mcxrln AP;[vҨA0AN@c*u0$@}zĜ =K_>+>:@$j(du>MX"K k=k&YHq`?qd_-e- Ξ&2O 4R8Iyw?l:+mBω(VO_ZN?5>~}4+ޯ rt_yJXD+nI& 53rͮ#MuxH13&@s@BV.G- 懿&H%+]WEWX2g9=5M,_IJ?VoÝ]$Lu=dbtMu;.Q \?!St)ŏlr$V{M(%dMdbcKH$Q \MOa,k=JzIm%jw 'mu24x3@.UrDoϒvB  Ii\,}>$gxIK?%vnZFBۥ)WcUp#xeI)]jw rdri6Pg~Z?!ZONHķ)kҷYlM!z2R;r$$eIzl`PG!uv:%ZͥMXA p&$=+x-jˑo}o2dfdu(*L}y͢!YR{rJ_fj79^y[Briʬ;j:Ag X]Z,3*].tI/Y KZ_.Zzb.җ혁Pd,!5A#49b6c~5:z3R}IPHW*OݸTI ̰CZBJ =ck侥O2RTX,Z^ӊ`ǴFG|a<]LdI)%mؕ#S=~#\R٦l5OFg"^u5$\5&VNnRBM䕉kUi?2>|7+!p5kH+뎱!vk'8["F]x[4;pVs:{r3uC\:[8*`n}oUl'~0?Jkr`FLʞ},YU|%. IDAT`W8-~b:&>1_DY?oBӵkQmCEwelE6.M|_ wlg³})}BBl:k"`\0%!A6}LvY; Fƚd9pT]Jp̞ "e|~;)-#l:s֫Z)=>psJBPs ]5a?e1xj3 NJI^sдdJR'NNlZCnk>Q&SP%Ǹ:pK\DO܇)෰4ޛ[̢,&sՈ5+?*57mU6!]SJL~a본ؼpuN! s`UdZ dT^rLȄqt@ˁg2$g9И:r3tm~{9ZHf|ogW[?u,_ÍCs7 fgncCWno>eМE6/cص!=isVwK6`}66-ہM.*Ru[I|#ī>W}+x|&nc|ży&YM1d׆3E94iMN#;rfqhAIldnoTO2BmEص>%/O~ \NWHQX=ိ+=n?'ZN\oksHR|\ bx/p}Y QQ΂!\ttZZ Wٵg,q Bm>P'Įpx=>4]c 2-b+"I1(/rӪk_Yg|Y33 lZrulKZpå-X3Z: l6_Lw9ZUo~ZDow/Clf)w]@J4-plcr^n%W߄o&\>yet3pYs|ډ h<oiP_mLB غ0EBkjh@z΀j w.dG(&˒4 ƴ$S [e>;5 Ւ uBAMc4! f76*B#`;Ccc?;+v0- bov)~Md]YXz+=݇яThl[гsI׶aŬki\/$[O3 X6V0@{ޗ]uDseFWjElc'"[ >{;bEi[I ~ܬ|ZtôL6-IZ)r!+~IZchGztUr?uPܞyN^9 ])s?ѻ{ M@\AZ]Y[xo.:1 0}SOvis+fngZ.3Z6c`$v r[]睩)mW0M; )%w=_:1!Du 72?>Շ7noFs8i69iX?KJm9LC˺k}<Ϟ~&ΉM@Bd]'eЭC$>fC9@8|{I/fR7Au 17ۅ4/31!Hn1$\66iմE9e G2_@{ -[zqq<:u [Dc-D2+)/qS/eQ9O'M:vآb\5).|6k3vsǍs0?-sϩ?^Gt4G1a q!˂,$ e~ݷv$ȃor4l„i|@v+rA3uB9Kܒ;-|rM=>~6!n- ^ԇ[`u |[R"`[ pq {$})#!=qEwq{{^L+pߺ $"gXfJ`;`h/e>+HxzOo!;4mܗ9qf9"ڒbu[qz:C ܷ7PXF`E{bտJY>`[n&^ܡDNV itijhKO]]nzzVX03 +'^R-#t! {7ލK\wq#=?1]*GGkq~@BNѩu=~c5jBݎX6cwucP{ M߀~ #,ɹp9uBo$# R?EBkD91~H* ,ju+H(ְcv)Krtݞl6|[*`xn!Wl)G?<RtzvMK e;Oؕ nğYN]PչuۏvC;Itrx*BPU[!7}B=>U2ss Sl:4AyZvo{ U*pݗIo4FBZ$j}yRh6~aё&}+Hʁ xDOo䭗?oCTl^ zoFΪf>Io[߮ ,(*jx|p 49۝[WF>ojQ_L8nk-Foq)kߒ,k46lƬ+Nɮ''"e7ĥwsӶF hY]BpBhwڄcG;6HnGdB+)k;_d}e26/G˘xu`ZYw6`PPaNZJ ֢eLFݪ$%)&{3Rx=kw99kF_˂=avpDryzWB51"M9T/ yN ߾ ,ؑWX?ZBbl=c&$KOřtD\ D}~S),M9.b{8F`1HBh8!, }HYM1h#^Cr>:Zl;r/cXTv~մaԟ {^{߱z%!8n  :]{@eY2--MΝ;WΝ;W۷=4㑻wǣϑRW>Neطr?Vp %+>PFP(رciѢQQQ\.~?Bn7^QeO>d)---4xA}Z% zm JKK>N)mf~xxpNPsq-2Ժw:l6gmηkҴi?J|guSd)= SL?jgO?K.<<31tPXf cǎeРAԩSL222$//Pkz!z)LN+AwO ]>,,jO ]hb'-e#!O=a$pƏf!KX IoB<G2E%}%ghBDP}B?d'_ HHn"%3Dn}w,{| W2R;]{J a0|g:xHKK#33Mr]wsN:w̆ _;vd\wu}ݤń'/]N.9C"u@3l*wwHr$$IOy+'$?ⷬgnH&zHY-0f[BrsMR8l0# C_ZF4%"OS,z2x#aTkKe#;z&'Ms€ոfaIKj֦u)(RT'[m۶Q\\iXeYlْ7x\ƏOaa!x^vyyGؾ};{trJ=>zл:)~3 G1[~9pt&\AðYRZ +`݁=i^.Ru52:$\k||l=n-SWb6Rn#_tQH}y.RT8*w)%W>믿i&ׯOtt4-[M(++cܹ 6-[v[.111| 8p򈋋#++F;U(owq1J+:L![ "DLH= ְc-.*кRҡ[&7w m[v%:Tc)`T׸c cOHJnԧI>hZ,&%抪p1ԩ^{-}M6Nz'66ÇimڴK.a1bqqqѴiS4MR*'-z:mU:!~lYk4cEHe*]8 RоꜦUu)[{ټn}|_1M$I nJoOU&PK:U-CGsЅt,V6'~Ӈi1 nCJFښ%/a3躁nsa,Y6.7}X󻁀^Lӏ@FGWG!躁aw1g{~[~ӏ -fG ,w Q "o ύ >]4σ7[^!B`Y&vύ6ÁXA lLaBmn\Pէwyaׄ`_q) Ysa/ w {%4{~Ȗˉېk.6kn0Ƿ9[hմ;]ͪvM>A.?vD7~dݥ<>sZ49IBx#{oHZ7!OcY5wؓn.`PaLa!hݜ?y[ҧFI>?ܼ48N 30uPVGBfJw²6Rj=~nsr괛%șw'2ilpvJw2;}1.gMm7^#M;c٣ܻb-e;«m絭/)셮?r-}g+=2}!Ool 0tL|l _]"v͎ΞE+ p'8oEjZpGYO SvPQsK]7;k^o XS&gpV|/|q:yop0RL`VYP(}<i&N;f%i qIDATi4w~QSWiϷ{W^68諟5;Ya27k#{eNT}hYcidgo~0X )>lvbL@HNIR 8vy.RjB>w{-t-䖬/aNV@[ff29Yw6v,`ͷ@td?KCIr&2,9sVZp9MϷr|kj95uәSSf&[d#yr:Mm5 nR2,Y3R}7Zl688[8^ i$ONg^=B=OfԹPJl.8TL# #|]NM2*s\#-L|uTh=$ĥ=$'e޾ dg?ҧ/{+]bSÁ~BSh4Wa |-Rs&[{'-5t.e'<\f`D?)C%"{r&D+t>,B.f,eMJ-|w<4tRxFe&[\JOg3ɫUUI$Ħ ؘ$ζTQT}LS9I 3awyIQ c$}ΦZr׬kS3V;1ކR M@L ,CPYw4͆Rj7$ SLݵ-},y}ʓD̼[gf܄h o*ߡkܾ70L@9l Iw>{ӻ[[F\_%T`- p'&s>R`&o ~>SqXLϞũO9+\+)s2ZpO~5BWCɖjȷSbk=Fq-6筞dF?u~զӕvwMLrFPSWBނ{IMA 9W `};No~ͧ]YBLe hV u-;Tvoj}!,fBCjhA޽\v3TOBH] s2} ٩!cL][C:ROCCfg\2!$>\ KY'^XXXXXX[OMW76sCjt (hl=ũG1L o3zhﬧc.|)mTz),Ʌ6LӤB3^]us5 S_bU(fm ϒ]ү./KnTWG$-F5&g+sEqQqDFq:).!$oWaH*OL@7.VO wE~l{gV}|&S muWQI!G^oy J7 $bXvwBxz<}9cNtD Sh=Haq/,Iʮ #{έ$'_A^ܛrmON $dŅv2%_vmgBWUGbu P]MBJwd}ǑBhv:n(6Q€Sޣ2Z!k6W ڮxHDzVc^URh:RPF֬ew#F :7RX]ooʏ;+TiyHM@zh ܶ e~tmM&'T_m*%{:}Mc=k{!5:q&C_hWzn|ohkSx6`{~nGXMM5| q]pb2μ5oE¿%_Rav\z 0ԦcDcogw5_FM<.[ R7 \,u6͎!? ^͉nHLt>)%ca#`ڵ/]es`rOByu?iCjޢc=x.lG6a1GzqoCd[.m-!ģ@j%OWij%p&Zd qG7] 6MPx$T[kZj2|]ݱWsok :p-J'l$MT_) U~s"gc%i[׽~'oʞp-~2Df B#M=g_B8sWB>eg*Bĕ[UTyKgy-'*IENDB`python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/xpy_button.png0000664000175000017500000002352712304063646026117 0ustar oliveroliverPNG  IHDRVbKGD̿ pHYs  tIMEDtEXtCommentCreated with GIMPW IDATxy}?snA$B<@ HJ$2a(UKJJ*U%[(uX(Y@%D&A;ݙ;;;=;>Ibw_/rJw[fE'YꪩY{[pD^wG7$W.QCa+)Di)| d)_=8s|vI>>׭2DF_wr/z+8&ï-R9a*u PRx̪w񕷮eiGv0tfB۹ [-ە[?¯cݎbo_UTVzbҖ?s6zSuMc Y\w.lu;\e ^yB욶f,z BJW^ (s߭$$RzI`!`%(9oJ ,+ԶT-H Gs\IWxI"UB͓U3b+ŢFI ;6I7]hoT4 ֧<:FyoMϕC*5$ʈi=gX=+ cOxL.B /*0 -U,ZB ڟF#SH`)M " Ǘ/۞H PCFn(~χK&jLs5;*(c`QKR9yb֚(eQ8T"h5ֈZĒi`O]o"8]9_aaʅWL2fuk5!Ҩ_v_BfqqhnJH$2llTCk,"Tj!+WNaW/X8xԂץ޽<"U\\fcLFVDዑ6ZͥLd ®!T ,T͇|jXxx]1d2oQv) vWx¦FmJ VI4 +/$ VIC}L>`W8f7P_ɤ4j3!oƌ毙OvIqVijX \^ u>Ç0$hnZɥ®zU'$ՅMPjN )g-rTU%C͇{594qU2!| UfY#u@Ȕ t . aPkV:GavP_uDSyI$%RJg)S) ("_t;INODE q*,Hn},$1i]ȤġQ2_(#Xqy(GG"y\0eZs5,FxTRv24/E% \s%[##p(l-|8Iaz}xAwaW)Qf&*x9ʎS,pfF9rsϬHPP}<Օۯ pTh=M֫^}I}$p܋kQn4C,a%v_\8<O I ʛ%ٗ22:$`a oT:Fi25^g5ABx>-f8l4VBqKcfx!x\û2$G & |2ݱܢgelbƬ!:%~e<5f8[n/ _RW+X6LG|`G83<$'E^Gcq %dT6eC#y]13AyINc!;. wbJƵN'\Y^S,#Drɲ(Q5XjLpx|D3g9#Gbqo0XAuwwE'xR5NDtD`q. l.S|G@MɲM]D 7d{L 5C/neW-cx/P=,5!*6`kaqa?2@78C1dƈQ 6'GD *5=Xv  '3Ivsm$uN0FQrd!X(7ug4U,dq7 P`G5^B>bYqEA*5S@x(+$_ lAy*D$Q G g3El,#ϻ$i=%\2pm+1/('}ԼXTQLVV1ȑ qd]DnP'*Fy*#dy'W-!I|!X3~㯺Eq (at-h7 E`SɧQdq]I$10$U >+ " G)bHpQ\e D`s+YXXk˧12ӱZq5; Nc%eep;tpȱT#"QT~b]TJ$/ؼY@V}.[g)YV

8Ũ1]:OkY.mí(82dT_TA:w@$)2d'|BP`02P1rX)R@.Ypdc*INwJfpy*^)wx6%.L5"̑+qɄ<(dF=)k>5 $9C,rdp4s}2e5MVɲ;q5T(EpsVqxaiNO~´H5U0Ca|a%OF +#B4/1up_@$ׯY+i0Q$v[#/N.aqv_>Kg8$F(.>G!d5ufYg j~(S1/xL3@bkxBl8K&oFx>¨ -&2z짝T],!Q u|IC EC/gx^anOJJxHTxcJq;gqQ'"PaB͂bN<.y> R F1I R445KGb?r(q%W #ϳWɑ YGD"k#ΊXg: R 2Ʊ8DF-yUFhS 81FoHX[\@f>Eimk^S/y]'k 9 $ǝ^k؏9<Q8[ȪjM8->cߚa3gaݼI^/ē*q<Q>C"Y2AG'=6к:,EFyt|GDsFcq#e /`'QaP !܏PǓσEA TvN|LmarpxRޠ,.eeDrӌ@y2\*= eY.U-\V3Arj`_E[Pc8ʕ|m"\Xw <ďXE]cҌe2RFEBe(!/Qd16R]gӌU=9Fl51M7xEdP8hdzL eY`YDO%FpHtIˏBա%Y Q;h|\" $9-q%YI0nnmLDSܭQ,yY0X,GB+='qc랂UzSOMWbX,aVx6.6% zqQB[ʾtfy۩&Qxk%zdHkoRYa EKY-pt2 %ϩ]'pPdp:{˲ NՑ9"~[X\1 )[I £Ct IHa2EH*D]dȰqrVsG)Z؍3[T;$bJ8 {4-<.7G1-h"Y=t!gϩKy/oZkEOKTdX A v} 4l~;$Hhlo>+lr,ᝌr:%\62J;rY8,5B<@5GMEbq擔ck!BWQV8ʲDeː4a I1cA &T56A;=U$ VaőZh AVDs 6}flH^'пZ2ΠJ{0V.D)>6{EW Q;Q64EOdOCAw$ND}7f[aKƫ8̷ʑ89*:*D-k(s-d b[m+,|,v I(sRdo!JW Yf5lYLz]a 8~e;xxmJfȐ7^Ox9G&Hj<padn2[ye&Y$ G*1Y1nI; 1 NL1֐ e"wޮu5S[/[㲕X3>bBLF= ;ֻAQ=ba!dT{; "C1[T1sZQ$Gި%=!"OFf6ڐxQlܐi^{v\% YA2d(%ͲN᷸l:8zrT `#.{DxGRe4w݉b*NJ5TClRJ|,9oQ+9DE9d#30R AlMdlh9Dh%ov%׵gb uݼC6زӿRs.9(2v`90y5yÌD gZTy;X #yHE%8YmtNLQdH"n\E>g\su:uU#؁fH!r7-C Lj*4kZ] a$a!']O|2 2U #X䃍5,sA>@aa'g6g9os|sOb  Q\p0c\P$q:"Lfa ¥SsK'(Q+lVta=cc#QB er 1;5L_MѲer7<屒QmTl:)5+cP!9MFC^)xGH(<U;1=&r7bkM8o+_[b1F,jc4O:~R&޳Kp!lcٜ^z Ɲؼr1yf#Q`,ͭcظ!yAb IYN-be~ yzI=}ͬ7za`jk1fΦ+v{!wCit;t,gm#y&&2XPd5m]7*G1#Scq(2:coQ˨}ZEH5 luIE:׻KNOsSP |~|z YN0b^LNS,yOG*1_gGF .g k |2CݶPV1QSe1=`;H 7JN23G_Z/(ʜ`O3mn}F8˝O8ėJ2)]\4Z=xՑjlg"cVCePc|ӌ/Зk <4.|v6KpBżq"CO9sp {c3x1)'X}\*~q%6x RTZOJz&s&M EHDک>r<~ď-1'ڮGu]K.z'yl(No^$3o`sCl"Ĺ=gV1RcL_03J@O|bv5f%31eY|.AWc(ˏ8hsAN\%ƍm6LQ$kaYX]y뗸SK!LCL,eZR0,1,A~ChI δ٩ $^M\)*ҝ~pONv7U-癤4wa1TH {S^+5LP ʱ4?РX̆zNjgLś0jB% "o/JN[;I &&)6H/T:3 fFMH2$ڠ.J4 $Oj-AB+B(%RWϹ!玣rOp{HߙbO~um[MȺYD*Pd䃧>נI!,x[6O(BH uTUHrO;v?wh"}|]kud4DiЭDVewro}>KJ+ ~J9jsB*(~jS6+G9nIENDB`python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/APIReferenceTool.rst0000664000175000017500000000050112304063646027001 0ustar oliveroliverAPI reference tool ================== Besides this online documentation, Expyriment includes a full offline API reference tool which will allow you to browse and search the API offline, using a graphical user interface. For more information on offline documentation, please call:: expyriment.show_documentation() python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Technical.rst0000664000175000017500000000025612304063646025614 0ustar oliveroliverTechnical issues ================ .. toctree:: :titlesonly: :maxdepth: 2 Hardware compatibility Timing Problems & Limitations python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/numpydoc/0000775000175000017500000000000012314561273025023 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/numpydoc/docscrape_sphinx.py0000664000175000017500000001711712314530373030735 0ustar oliveroliverimport re, inspect, textwrap, pydoc import sphinx from docscrape import NumpyDocString, FunctionDoc, ClassDoc class SphinxDocString(NumpyDocString): def __init__(self, docstring, config={}): self.use_plots = config.get('use_plots', False) NumpyDocString.__init__(self, docstring, config=config) # string conversion routines def _str_header(self, name, symbol='`'): return ['.. rubric:: ' + name, ''] def _str_field_list(self, name): return [':' + name + ':'] def _str_indent(self, doc, indent=4): out = [] for line in doc: out += [' '*indent + line] return out def _str_signature(self): return [''] if self['Signature']: return ['``%s``' % self['Signature']] + [''] else: return [''] def _str_summary(self): return self['Summary'] + [''] def _str_extended_summary(self): return self['Extended Summary'] + [''] def _str_param_list(self, name): out = [] if self[name]: out += self._str_field_list(name) out += [''] for param,param_type,desc in self[name]: out += self._str_indent(['**%s** : %s' % (param.strip(), param_type)]) out += [''] out += self._str_indent(desc,8) out += [''] return out @property def _obj(self): if hasattr(self, '_cls'): return self._cls elif hasattr(self, '_f'): return self._f return None def _str_member_list(self, name): """ Generate a member listing, autosummary:: table where possible, and a table where not. """ out = [] if self[name]: out += ['.. rubric:: %s' % name, ''] prefix = getattr(self, '_name', '') if prefix: prefix = '~%s.' % prefix autosum = [] others = [] for param, param_type, desc in self[name]: param = param.strip() if not self._obj or hasattr(self._obj, param): autosum += [" %s%s" % (prefix, param)] else: others.append((param, param_type, desc)) if autosum: out += ['.. autosummary::', ' :toctree:', ''] out += autosum if others: maxlen_0 = max([len(x[0]) for x in others]) maxlen_1 = max([len(x[1]) for x in others]) hdr = "="*maxlen_0 + " " + "="*maxlen_1 + " " + "="*10 fmt = '%%%ds %%%ds ' % (maxlen_0, maxlen_1) n_indent = maxlen_0 + maxlen_1 + 4 out += [hdr] for param, param_type, desc in others: out += [fmt % (param.strip(), param_type)] out += self._str_indent(desc, n_indent) out += [hdr] out += [''] return out def _str_section(self, name): out = [] if self[name]: out += self._str_header(name) out += [''] content = textwrap.dedent("\n".join(self[name])).split("\n") out += content out += [''] return out def _str_see_also(self, func_role): out = [] if self['See Also']: see_also = super(SphinxDocString, self)._str_see_also(func_role) out = ['.. seealso::', ''] out += self._str_indent(see_also[2:]) return out def _str_warnings(self): out = [] if self['Warnings']: out = ['.. warning::', ''] out += self._str_indent(self['Warnings']) return out def _str_index(self): idx = self['index'] out = [] if len(idx) == 0: return out out += ['.. index:: %s' % idx.get('default','')] for section, references in idx.iteritems(): if section == 'default': continue elif section == 'refguide': out += [' single: %s' % (', '.join(references))] else: out += [' %s: %s' % (section, ','.join(references))] return out def _str_references(self): out = [] if self['References']: out += self._str_header('References') if isinstance(self['References'], str): self['References'] = [self['References']] out.extend(self['References']) out += [''] # Latex collects all references to a separate bibliography, # so we need to insert links to it if sphinx.__version__ >= "0.6": out += ['.. only:: latex',''] else: out += ['.. latexonly::',''] items = [] for line in self['References']: m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I) if m: items.append(m.group(1)) out += [' ' + ", ".join(["[%s]_" % item for item in items]), ''] return out def _str_examples(self): examples_str = "\n".join(self['Examples']) if (self.use_plots and 'import matplotlib' in examples_str and 'plot::' not in examples_str): out = [] out += self._str_header('Examples') out += ['.. plot::', ''] out += self._str_indent(self['Examples']) out += [''] return out else: return self._str_section('Examples') def __str__(self, indent=0, func_role="obj"): out = [] out += self._str_signature() out += self._str_index() + [''] out += self._str_summary() out += self._str_extended_summary() for param_list in ('Parameters', 'Returns', 'Other Parameters', 'Raises', 'Warns'): out += self._str_param_list(param_list) out += self._str_warnings() out += self._str_see_also(func_role) out += self._str_section('Notes') out += self._str_references() out += self._str_examples() for param_list in ('Attributes', 'Methods'): out += self._str_member_list(param_list) out = self._str_indent(out,indent) return '\n'.join(out) class SphinxFunctionDoc(SphinxDocString, FunctionDoc): def __init__(self, obj, doc=None, config={}): self.use_plots = config.get('use_plots', False) FunctionDoc.__init__(self, obj, doc=doc, config=config) class SphinxClassDoc(SphinxDocString, ClassDoc): def __init__(self, obj, doc=None, func_doc=None, config={}): self.use_plots = config.get('use_plots', False) ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config) class SphinxObjDoc(SphinxDocString): def __init__(self, obj, doc=None, config={}): self._f = obj SphinxDocString.__init__(self, doc, config=config) def get_doc_object(obj, what=None, doc=None, config={}): if what is None: if inspect.isclass(obj): what = 'class' elif inspect.ismodule(obj): what = 'module' elif callable(obj): what = 'function' else: what = 'object' if what == 'class': return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, config=config) elif what in ('function', 'method'): return SphinxFunctionDoc(obj, doc=doc, config=config) else: if doc is None: doc = pydoc.getdoc(obj) return SphinxObjDoc(obj, doc, config=config) python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/numpydoc/numpydoc.py0000664000175000017500000001271312314530373027234 0ustar oliveroliver""" ======== numpydoc ======== Sphinx extension that handles docstrings in the Numpy standard format. [1] It will: - Convert Parameters etc. sections to field lists. - Convert See Also section to a See also entry. - Renumber references. - Extract the signature from the docstring, if it can't be determined otherwise. .. [1] http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines#docstring-standard """ import os, re, pydoc from docscrape_sphinx import get_doc_object, SphinxDocString from sphinx.util.compat import Directive import inspect def mangle_docstrings(app, what, name, obj, options, lines, reference_offset=[0]): cfg = dict(use_plots=app.config.numpydoc_use_plots, show_class_members=app.config.numpydoc_show_class_members) if what == 'module': # Strip top title title_re = re.compile(ur'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', re.I|re.S) lines[:] = title_re.sub(u'', u"\n".join(lines)).split(u"\n") else: doc = get_doc_object(obj, what, u"\n".join(lines), config=cfg) lines[:] = unicode(doc).split(u"\n") if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ obj.__name__: if hasattr(obj, '__module__'): v = dict(full_name=u"%s.%s" % (obj.__module__, obj.__name__)) else: v = dict(full_name=obj.__name__) lines += [u'', u'.. htmlonly::', ''] lines += [u' %s' % x for x in (app.config.numpydoc_edit_link % v).split("\n")] # replace reference numbers so that there are no duplicates references = [] for line in lines: line = line.strip() m = re.match(ur'^.. \[([a-z0-9_.-])\]', line, re.I) if m: references.append(m.group(1)) # start renaming from the longest string, to avoid overwriting parts references.sort(key=lambda x: -len(x)) if references: for i, line in enumerate(lines): for r in references: if re.match(ur'^\d+$', r): new_r = u"R%d" % (reference_offset[0] + int(r)) else: new_r = u"%s%d" % (r, reference_offset[0]) lines[i] = lines[i].replace(u'[%s]_' % r, u'[%s]_' % new_r) lines[i] = lines[i].replace(u'.. [%s]' % r, u'.. [%s]' % new_r) reference_offset[0] += len(references) def mangle_signature(app, what, name, obj, options, sig, retann): # Do not try to inspect classes that don't define `__init__` if (inspect.isclass(obj) and (not hasattr(obj, '__init__') or 'initializes x; see ' in pydoc.getdoc(obj.__init__))): return '', '' if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return if not hasattr(obj, '__doc__'): return doc = SphinxDocString(pydoc.getdoc(obj)) if doc['Signature']: sig = re.sub(u"^[^(]*", u"", doc['Signature']) return sig, u'' def setup(app, get_doc_object_=get_doc_object): global get_doc_object get_doc_object = get_doc_object_ app.connect('autodoc-process-docstring', mangle_docstrings) app.connect('autodoc-process-signature', mangle_signature) app.add_config_value('numpydoc_edit_link', None, False) app.add_config_value('numpydoc_use_plots', None, False) app.add_config_value('numpydoc_show_class_members', True, True) # Extra mangling domains app.add_domain(NumpyPythonDomain) app.add_domain(NumpyCDomain) #------------------------------------------------------------------------------ # Docstring-mangling domains #------------------------------------------------------------------------------ from docutils.statemachine import ViewList from sphinx.domains.c import CDomain from sphinx.domains.python import PythonDomain class ManglingDomainBase(object): directive_mangling_map = {} def __init__(self, *a, **kw): super(ManglingDomainBase, self).__init__(*a, **kw) self.wrap_mangling_directives() def wrap_mangling_directives(self): for name, objtype in self.directive_mangling_map.items(): self.directives[name] = wrap_mangling_directive( self.directives[name], objtype) class NumpyPythonDomain(ManglingDomainBase, PythonDomain): name = 'np' directive_mangling_map = { 'function': 'function', 'class': 'class', 'exception': 'class', 'method': 'function', 'classmethod': 'function', 'staticmethod': 'function', 'attribute': 'attribute', } class NumpyCDomain(ManglingDomainBase, CDomain): name = 'np-c' directive_mangling_map = { 'function': 'function', 'member': 'attribute', 'macro': 'function', 'type': 'class', 'var': 'object', } def wrap_mangling_directive(base_directive, objtype): class directive(base_directive): def run(self): env = self.state.document.settings.env name = None if self.arguments: m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0]) name = m.group(2).strip() if not name: name = self.arguments[0] lines = list(self.content) mangle_docstrings(env.app, objtype, name, None, None, lines) self.content = ViewList(lines, self.content.parent) return base_directive.run(self) return directive python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/numpydoc/docscrape.py0000664000175000017500000003570512314530373027347 0ustar oliveroliver"""Extract reference documentation from the NumPy source tree. """ import inspect import textwrap import re import pydoc from StringIO import StringIO from warnings import warn class Reader(object): """A line-based string reader. """ def __init__(self, data): """ Parameters ---------- data : str String with lines separated by '\n'. """ if isinstance(data,list): self._str = data else: self._str = data.split('\n') # store string as list of lines self.reset() def __getitem__(self, n): return self._str[n] def reset(self): self._l = 0 # current line nr def read(self): if not self.eof(): out = self[self._l] self._l += 1 return out else: return '' def seek_next_non_empty_line(self): for l in self[self._l:]: if l.strip(): break else: self._l += 1 def eof(self): return self._l >= len(self._str) def read_to_condition(self, condition_func): start = self._l for line in self[start:]: if condition_func(line): return self[start:self._l] self._l += 1 if self.eof(): return self[start:self._l+1] return [] def read_to_next_empty_line(self): self.seek_next_non_empty_line() def is_empty(line): return not line.strip() return self.read_to_condition(is_empty) def read_to_next_unindented_line(self): def is_unindented(line): return (line.strip() and (len(line.lstrip()) == len(line))) return self.read_to_condition(is_unindented) def peek(self,n=0): if self._l + n < len(self._str): return self[self._l + n] else: return '' def is_empty(self): return not ''.join(self._str).strip() class NumpyDocString(object): def __init__(self, docstring, config={}): docstring = textwrap.dedent(docstring).split('\n') self._doc = Reader(docstring) self._parsed_data = { 'Signature': '', 'Summary': [''], 'Extended Summary': [], 'Parameters': [], 'Returns': [], 'Raises': [], 'Warns': [], 'Other Parameters': [], 'Attributes': [], 'Methods': [], 'See Also': [], 'Notes': [], 'Warnings': [], 'References': '', 'Examples': '', 'index': {} } self._parse() def __getitem__(self,key): return self._parsed_data[key] def __setitem__(self,key,val): if not self._parsed_data.has_key(key): warn("Unknown section %s" % key) else: self._parsed_data[key] = val def _is_at_section(self): self._doc.seek_next_non_empty_line() if self._doc.eof(): return False l1 = self._doc.peek().strip() # e.g. Parameters if l1.startswith('.. index::'): return True l2 = self._doc.peek(1).strip() # ---------- or ========== return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) def _strip(self,doc): i = 0 j = 0 for i,line in enumerate(doc): if line.strip(): break for j,line in enumerate(doc[::-1]): if line.strip(): break return doc[i:len(doc)-j] def _read_to_next_section(self): section = self._doc.read_to_next_empty_line() while not self._is_at_section() and not self._doc.eof(): if not self._doc.peek(-1).strip(): # previous line was empty section += [''] section += self._doc.read_to_next_empty_line() return section def _read_sections(self): while not self._doc.eof(): data = self._read_to_next_section() name = data[0].strip() if name.startswith('..'): # index section yield name, data[1:] elif len(data) < 2: yield StopIteration else: yield name, self._strip(data[2:]) def _parse_param_list(self,content): r = Reader(content) params = [] while not r.eof(): header = r.read().strip() if ' : ' in header: arg_name, arg_type = header.split(' : ')[:2] else: arg_name, arg_type = header, '' desc = r.read_to_next_unindented_line() desc = dedent_lines(desc) params.append((arg_name,arg_type,desc)) return params _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) def _parse_see_also(self, content): """ func_name : Descriptive text continued text another_func_name : Descriptive text func_name1, func_name2, :meth:`func_name`, func_name3 """ items = [] def parse_item_name(text): """Match ':role:`name`' or 'name'""" m = self._name_rgx.match(text) if m: g = m.groups() if g[1] is None: return g[3], None else: return g[2], g[1] raise ValueError("%s is not a item name" % text) def push_item(name, rest): if not name: return name, role = parse_item_name(name) items.append((name, list(rest), role)) del rest[:] current_func = None rest = [] for line in content: if not line.strip(): continue m = self._name_rgx.match(line) if m and line[m.end():].strip().startswith(':'): push_item(current_func, rest) current_func, line = line[:m.end()], line[m.end():] rest = [line.split(':', 1)[1].strip()] if not rest[0]: rest = [] elif not line.startswith(' '): push_item(current_func, rest) current_func = None if ',' in line: for func in line.split(','): if func.strip(): push_item(func, []) elif line.strip(): current_func = line elif current_func is not None: rest.append(line.strip()) push_item(current_func, rest) return items def _parse_index(self, section, content): """ .. index: default :refguide: something, else, and more """ def strip_each_in(lst): return [s.strip() for s in lst] out = {} section = section.split('::') if len(section) > 1: out['default'] = strip_each_in(section[1].split(','))[0] for line in content: line = line.split(':') if len(line) > 2: out[line[1]] = strip_each_in(line[2].split(',')) return out def _parse_summary(self): """Grab signature (if given) and summary""" if self._is_at_section(): return summary = self._doc.read_to_next_empty_line() summary_str = " ".join([s.strip() for s in summary]).strip() if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): self['Signature'] = summary_str if not self._is_at_section(): self['Summary'] = self._doc.read_to_next_empty_line() else: self['Summary'] = summary if not self._is_at_section(): self['Extended Summary'] = self._read_to_next_section() def _parse(self): self._doc.reset() self._parse_summary() for (section,content) in self._read_sections(): if not section.startswith('..'): section = ' '.join([s.capitalize() for s in section.split(' ')]) if section in ('Parameters', 'Returns', 'Raises', 'Warns', 'Other Parameters', 'Attributes', 'Methods'): self[section] = self._parse_param_list(content) elif section.startswith('.. index::'): self['index'] = self._parse_index(section, content) elif section == 'See Also': self['See Also'] = self._parse_see_also(content) else: self[section] = content # string conversion routines def _str_header(self, name, symbol='-'): return [name, len(name)*symbol] def _str_indent(self, doc, indent=4): out = [] for line in doc: out += [' '*indent + line] return out def _str_signature(self): if self['Signature']: return [self['Signature'].replace('*','\*')] + [''] else: return [''] def _str_summary(self): if self['Summary']: return self['Summary'] + [''] else: return [] def _str_extended_summary(self): if self['Extended Summary']: return self['Extended Summary'] + [''] else: return [] def _str_param_list(self, name): out = [] if self[name]: out += self._str_header(name) for param,param_type,desc in self[name]: out += ['%s : %s' % (param, param_type)] out += self._str_indent(desc) out += [''] return out def _str_section(self, name): out = [] if self[name]: out += self._str_header(name) out += self[name] out += [''] return out def _str_see_also(self, func_role): if not self['See Also']: return [] out = [] out += self._str_header("See Also") last_had_desc = True for func, desc, role in self['See Also']: if role: link = ':%s:`%s`' % (role, func) elif func_role: link = ':%s:`%s`' % (func_role, func) else: link = "`%s`_" % func if desc or last_had_desc: out += [''] out += [link] else: out[-1] += ", %s" % link if desc: out += self._str_indent([' '.join(desc)]) last_had_desc = True else: last_had_desc = False out += [''] return out def _str_index(self): idx = self['index'] out = [] out += ['.. index:: %s' % idx.get('default','')] for section, references in idx.iteritems(): if section == 'default': continue out += [' :%s: %s' % (section, ', '.join(references))] return out def __str__(self, func_role=''): out = [] out += self._str_signature() out += self._str_summary() out += self._str_extended_summary() for param_list in ('Parameters', 'Returns', 'Other Parameters', 'Raises', 'Warns'): out += self._str_param_list(param_list) out += self._str_section('Warnings') out += self._str_see_also(func_role) for s in ('Notes','References','Examples'): out += self._str_section(s) for param_list in ('Attributes', 'Methods'): out += self._str_param_list(param_list) out += self._str_index() return '\n'.join(out) def indent(str,indent=4): indent_str = ' '*indent if str is None: return indent_str lines = str.split('\n') return '\n'.join(indent_str + l for l in lines) def dedent_lines(lines): """Deindent a list of lines maximally""" return textwrap.dedent("\n".join(lines)).split("\n") def header(text, style='-'): return text + '\n' + style*len(text) + '\n' class FunctionDoc(NumpyDocString): def __init__(self, func, role='func', doc=None, config={}): self._f = func self._role = role # e.g. "func" or "meth" if doc is None: if func is None: raise ValueError("No function or docstring given") doc = inspect.getdoc(func) or '' NumpyDocString.__init__(self, doc) if not self['Signature'] and func is not None: func, func_name = self.get_func() try: # try to read signature argspec = inspect.getargspec(func) argspec = inspect.formatargspec(*argspec) argspec = argspec.replace('*','\*') signature = '%s%s' % (func_name, argspec) except TypeError, e: signature = '%s()' % func_name self['Signature'] = signature def get_func(self): func_name = getattr(self._f, '__name__', self.__class__.__name__) if inspect.isclass(self._f): func = getattr(self._f, '__call__', self._f.__init__) else: func = self._f return func, func_name def __str__(self): out = '' func, func_name = self.get_func() signature = self['Signature'].replace('*', '\*') roles = {'func': 'function', 'meth': 'method'} if self._role: if not roles.has_key(self._role): print "Warning: invalid role %s" % self._role out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''), func_name) out += super(FunctionDoc, self).__str__(func_role=self._role) return out class ClassDoc(NumpyDocString): def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, config={}): if not inspect.isclass(cls) and cls is not None: raise ValueError("Expected a class or None, but got %r" % cls) self._cls = cls if modulename and not modulename.endswith('.'): modulename += '.' self._mod = modulename if doc is None: if cls is None: raise ValueError("No class or documentation string given") doc = pydoc.getdoc(cls) NumpyDocString.__init__(self, doc) if config.get('show_class_members', True): if not self['Methods']: self['Methods'] = [(name, '', '') for name in sorted(self.methods)] if not self['Attributes']: self['Attributes'] = [(name, '', '') for name in sorted(self.properties)] @property def methods(self): if self._cls is None: return [] return [name for name,func in inspect.getmembers(self._cls) if not name.startswith('_') and callable(func)] @property def properties(self): if self._cls is None: return [] return [name for name,func in inspect.getmembers(self._cls) if not name.startswith('_') and func is None] python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/numpydoc/__init__.py0000664000175000017500000000003312314530373027125 0ustar oliveroliverfrom numpydoc import setup python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/numpydoc/LICENSE.txt0000664000175000017500000001362312314530373026650 0ustar oliveroliver------------------------------------------------------------------------------- The files - numpydoc.py - autosummary.py - autosummary_generate.py - docscrape.py - docscrape_sphinx.py - phantom_import.py have the following license: Copyright (C) 2008 Stefan van der Walt , Pauli Virtanen Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- The files - compiler_unparse.py - comment_eater.py - traitsdoc.py have the following license: This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative. Copyright (c) 2006, Enthought, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Enthought, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- The files - only_directives.py - plot_directive.py originate from Matplotlib (http://matplotlib.sf.net/) which has the following license: Copyright (c) 2002-2008 John D. Hunter; All Rights Reserved. 1. This LICENSE AGREEMENT is between John D. Hunter (“JDH”), and the Individual or Organization (“Licensee”) accessing and otherwise using matplotlib software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, JDH hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use matplotlib 0.98.3 alone or in any derivative version, provided, however, that JDH’s License Agreement and JDH’s notice of copyright, i.e., “Copyright (c) 2002-2008 John D. Hunter; All Rights Reserved” are retained in matplotlib 0.98.3 alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates matplotlib 0.98.3 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to matplotlib 0.98.3. 4. JDH is making matplotlib 0.98.3 available to Licensee on an “AS IS” basis. JDH MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, JDH MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF MATPLOTLIB 0.98.3 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. JDH SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF MATPLOTLIB 0.98.3 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING MATPLOTLIB 0.98.3, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between JDH and Licensee. This License Agreement does not grant permission to use JDH trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using matplotlib 0.98.3, Licensee agrees to be bound by the terms and conditions of this License Agreement. python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Timing.rst0000664000175000017500000002327012304063646025152 0ustar oliveroliverTiming and empirical testing of Expyriment ========================================== **How accurate is the timing in Expyriment?** In general, the Expyriment clock can feature up to 1 ms accuracy. The exact timing, however, is subject to various factors, which we discuss here. Time and compatibility issues can be conveniently tested using the :doc:`Expyriment test suite `. Stimulus presentation --------------------- Visual ~~~~~~ Computer screens are updated according to their refresh rate. What happens then is that the whole screen is redrawn line by line all the time. For example, with a refresh rate of 60Hz, the screen is redrawn 60 times per second (1000ms) and the duration it takes to redraw it line by line is 16.66 ms (1000/60). When attempting to redraw the screen while it is currently already being updated (the lines are drawn) the result might lead to artifacts, since the update occurs immediately, leading to parts of both, the new and the old screen content, being visible. What is even worse is that you will never know in which phase of the redrawing the new redraw was started. Thus, you cannot be sure when exactly the new content is fully visible on screen. The first step towards getting around this problem is to synchronize the actual redraw to the vertical retrace of the screen. This means that a change in content will never happen immediately, but always only when the retrace is at the top left position. When synchronizing to the vertical retrace, the graphic card is told to update the screen the next time it starts redrawing the first line. While this will solve the problem of artifacts, you will still face the problem of not knowing when exactly something was visible on the screen, since the graphic card handles this synchronization itself in the background. Solving this problem is the exact (and only) reason why Expyriment uses OpenGL by default. It allows to wait for the vertical retrace to actually happen before proceeding with the code that tells the graphics card to update the screen (this is also known as blocking on the vertical retrace). This means that whenever something should be presented on screen, no matter in which line the redraw is at this point in time, the graphic card will wait for the redraw to be in the first line and then present the stimulus. Since the code is blocking, the time Expyriment reports the stimulus to be presented on screen will always be the time when the redraw is starting at the first line. Coming back to the example of the small dot in the center of the screen: Expyriment will correctly report a longer presentation time when the redraw has been just over the center line when the screen update was issued. *It is important to set your graphic card's driver settings to support synchronizing to the vertical retrace ("Sync to VBlank" or "V-sync") and to switch off any power saving schemes on the graphic card.* **Test results** We tested the visual timing by presented a white and a black fullscreen rectangle directly after each other. The brightness of the left upper edge of the screen was recorded using an optic sensor attached to an oscilloscope. After each screen presentation, a marker was send via the serial port to the oscilloscope. Testing was done on an Intel Core Duo PC with an Nvidia Quadro NVS 290 graphics card, running Microsoft Windows XP SP3. The monitor used was a Samsung SyncMaster 2233. Expyriment was running in OpenGL mode (default). The results revealed: * When presenting a stimulus, updating the screen does successfully block code execution until the vertical retrace actually happens. This can be seen in Figure 1, where the marker (yellow) lines up with the onset and offset of an increase in brightness (turquoise) which represent the onset of the white and the onset of the black screen, respectively. * Presenting (preloaded) stimuli can accurately be done each refresh rate (Figure 2). Visual stimulus presentation is time locked to the vertical retrace. .. image:: timing_visual1.png Visual stimuli can be presented each refresh rate. .. image:: timing_visual2.png Audio ----- Playing back audio is handled by PyGame. The present() and play() methods of auditory stimuli will return immediately. Since the audio stream has to be sent to the hardware, there will still be a delay before the audio can be heard. Unfortunately, the latency of the sound onset is not known by Expyriment. However, it is assumed to be relatively stable over time. Setting the audio buffersize to a smaller value than the default can decrease the delay, but might result in distorted audio. *It is important to set your samplerate, bitdepth and audio buffersize correctly. Setting the buffersize too low will result in distorted audio!* **Test results** We tested the audio timing by repeatedly playing back a beep tone (a 1 second sine wave). The output of the sound card was measured by an oscilloscope. Before starting playback of the beep, a marker was send via the serial port to the oscilloscope. Testing was done on an Intel Core Duo PC with a Soundblaster Audigy sound card, running Microsoft Windows XP SP3. In Expyriment, the samplerate was set to 44100 Hz, bitdepth to 16 bit and the buffersize equaled 128. The results revealed: * Audio playback was subject to a latency of maximally 20 ms. Figure 1 shows the maximal measured latency between the start of the playback (yellow) and the onset of the sound (turquoise). * This latency was relatively stable with a jitter of 5 ms. Figure 2 shows the minimal latency we could measure. Maximal measured audio latency. .. image:: timing_audio2.png Minimal measured audio latency .. image:: timing_audio1.png Video ~~~~~ Video presentation is a tricky subject. In Expyriment, the present() method of a video stimulus will start playback and present the first (current) frame on the screen. Thus, visual onset of this frame will be synchronized with the vertical retrace (see visual stimulus presentation above). Each following frame has to be plotted on the screen and the screen has to be updated. The wait_end() method of a video stimulus will automatically present each frame on the screen until the video is over. When Expyriment is in OpenGL mode, the process of plotting each frame might take longer than one refresh rate which will result in dropping frames (e.g. frames not being presented at all). To control for this, the wait_end() method will report and log if any frames were dropped during video playback. *Unfortunetly, right now, Expyriment can only handle MPEG-1 encoded videos!* Measuring user input -------------------- In Expyriment all inputs (keybard, mouse, gameport, serial port, parallel port) can be checked by directly polling them (via the wait() methods of the corresponding io object). This allows for the most accurate timing possible. Since Python wraps C functions for getting the system time, the accuracy is even more precise than milliseconds (which is the unit Expyriment uses). Expyriment does *not* have a main event loop (i.e. it will not automatically check for any incoming events in the background)! This was a design decision, since we think that in 99% of all cases the time of the user input is specified in the design and thus know beforehand (e.g. a response after a stimulus onset). Adding an event loop would make things unnecessarily more complicated for those 99%. However, we also thought of those cases that need to check user input during other operations: All events can manually be pushed from either Pygame's event cue (keyboard, mouse, joystick) or the operating system's buffer (serial port, parallel port) into an EventBuffer object. Doing this regularly is up to the user. Keyboard ~~~~~~~~ Keyboards (PS2 and USB) are known to have poor timing accuracy. Usually these are in the range of several 100th of a second. **Test results** We tested the timing of a Logitec USB keyboard in Windows XP SP3 using optical tracking. Our results revealed: * A timing accuracy between 20 and 26 ms. Mouse ~~~~~ On most operating systems, USB mice are polled at a rate of 8 ms. Mice with special drivers might be set to poll more often. **Test results** We tested the mouse accuracy of a standard USB mouse on Windows XP SP3 by measuring the time between reported position changes. Our results revealed: * The expected standard accuracy of 8 ms. * Using a Logitec G700 USB mouse with a dedicated driver, polling rates could A be reduced, leading to an increased accuracy of 1 ms. Serial port ~~~~~~~~~~~ The serial port is very accurate and thus suited for timing accurate measurements. If a computer does not have a serial port, USB-to-serial converter can be used (e.g. from Sweex or Keyspan). However, the timing accuracy of these depends on the implementation and drivers used! *It is important to deactivate any additional FIFO buffers or delays, provided by the port driver!* **Test results** We tested the timing of a UART 16550A serial port (a real one, not a USB-to-serial converter!) on Windows XP SP3 by sending a byte to a connected loopback device which immediately sends the byte back. We then measured the time between sending and receiving. We repeated this process 1000 times. Our results revealed: * With a baudrate of 115200, the maximal measured time between sending and receiving a byte was 0.283894736842 ms. * With a baudrate of 19200, the maximal measured time between sending and receiving a byte was 0.689593984962 ms. Parallel port ~~~~~~~~~~~~~ The parallel port works by directly applying a current (writing) and measuring if a current is applied (sending) to several pins on the connector. Expyriment is only able to read from Acknowledge, Paper-Out and Selected pins! python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Plugins.rst0000664000175000017500000000322712304064475025345 0ustar oliveroliverThe Expyriment plugin system (extras) ===================================== Usage ----- The design, stimuli, io and misc packages can be extended with plugins (additional classes) that can be accessed via the 'extras' namespace of each package. There are two locations Expyriment will look for installed plugins: 1. In the 'extras' directories of the corresponding packages of the Expyriment installation. 2. In the 'design', 'stmuli', 'io' and 'misc' direcories within a '.expyriment' or '~expyriment' directory located in the current user's home directory In both cases, plugins will be integrated into the '.extras' namespace of each package (e.g. expyriment.stimuli.extras.DotCloud). Development ----------- Basically, extra plugins are a simple python module with a single class, where the filename is the class name in lowercase. Additionally a file called 'classname_defaults.py' can be created which will hold the default values for all parameters given when initializing the class. The naming convention is 'classname_parameter'. For design and misc extras this is all there is, but for io and stimuli plugins, additional conventions need to be taken care of. io.extras ~~~~~~~~~ IO plugins have to inherit from 'expyriment.io.input' or 'expyriment.io.output' or both. This means they can also inherit from any other io class. stimuli.extras ~~~~~~~~~~~~~~ Stimulus plugins have to inherit from 'expyriment.stimuli.Stimulus'. This means they can also inherit from any other stimulus class. Additionally, every extra stimulus class needs a '_create_surface' method that defines what happens when the stimulus is preloaded, plotted or presented. python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Testsuite.rst0000664000175000017500000000256712314530407025715 0ustar oliveroliverExpyriment test suite ===================== The Expyriment test suite is a guided tool for testing your computer's abilities/performance. This includes timing accuracy of visual stimulus presentation, audio playback functionality, mouse functionality and serial port functionality/usage. Eventually, all test results can be saved as a protocol, together with some information about the system. **Starting the test suite** The test suite can either be started from within an experiment, or from an interactive Python session (for instance with IPython). To start the test suite, just call:: expyriment.control.run_test_suite() **Menu overview** Here is a brief explanation of the available options: 1. *Visual stimulus presentation* * Tests if stimuli can be presented timing accurately * Tests if stimulus presentation is synchronized to the refresh rate of the screen * Tests the video card's settings for buffering 2. *Auditory stimulus presentation* * Tests functionality of audio playback 3. *Font viewer* * Test installed fonts 4. *Mouse test* * Tests mouse accuracy (polling time) * Tests functionality of mouse buttons 5. *Serial port test* * Tests functionality of devices connected via the serial port 6. *Write protocol* * Saves all test results, as well as information about the system, as a text file. 7. *Quit* * Quits the test suite python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Installation.Android.rst0000664000175000017500000000317412304063646027744 0ustar oliveroliver.. _Android: Platform-specific instructions: Android ======================================= Introduction ------------ With Python and Pygame being ported to Android (`PGS4A`_), it is in principle possible to use Expyriment on Android devices, however, without OpenGL support. This can be achieved by compiling Python, Pygame and Expyriment into a Java app, using `PGS4A`_. For ease of use, we provide the "Expyriment Android Runtime", an Android application which can be used to directly run experiments on an Andoroid device (with Android > 2.2). Installing Expyriment --------------------- The easiest way to run experiments on Android devices is to use our "Expyriment Android Runtime" appplication. You can only download the current version from our `Android download page`_. In the future it will also be available in the Google Play Store. Installing Expyriment scripts ----------------------------- Once installed, the application will look for Expyriment scripts (each in its own subdirectory) in a directory called 'expyriment', located at the root level of either storage device under 'mnt' (i.e. the internal or external SD card). Examples of correctly located Expyriment scripts include: /mnt/sdcard0/expyriment/exp1/exp1.py /mnt/sdcard0/expyriment/exp2/exp2.py /mnt/extSdCard/expyriment/exp3/exp3.py /mnt/extSdCard/expyriment/exp4/exp4.py Notes ----- **Extra plugins not supported** The current version of the "Expyriment Android Runtime" does not support extras plugins. .. _`PGS4A`: http://pygame.renpy.org .. _`Android download page`: https://github.com/expyriment/expyriment-android-runtime/releases python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/conf.py0000664000175000017500000002045512304063646024472 0ustar oliveroliver# -*- coding: utf-8 -*- # # Expyriment documentation build configuration file, created by # sphinx-quickstart on Fri Apr 5 15:08:26 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os, time # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.viewcode', 'numpydoc'] # How to install numpydoc? --> sudo easy_install numpydoc # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Expyriment' copyright = u'{0}, Florian Krause & Oliver Lindemann'.format( time.localtime().tm_year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # file_path = os.path.split(os.path.abspath(__file__))[0] p = os.path.abspath('{0}/../../CHANGES.md'.format(file_path)) version_nr = "{0}" with open(p) as f: for line in f: if line[0:8].lower() == "upcoming": version_nr += "+" if line[0:7] == "Version": version_nr = version_nr.format(line[8:13]) break # The short X.Y version. version = version_nr[:3] # The full version, including alpha/beta/rc tags. release = version_nr # Download links download_exe = "https://github.com/expyriment/expyriment/releases/download/v{0}/expyriment-{0}-win32.exe".format(release) download_source = "https://github.com/expyriment/expyriment/releases/download/v{0}/expyriment-{0}.zip".format(release) rst_epilog = ".. |download_exe| replace:: {0}".format(download_exe) rst_epilog = rst_epilog + "\n" + ".. |download_source| replace:: {0}".format(download_source) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # 'default', 'sphinxdoc', 'haiku' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "sidebarbgcolor": "#222222", "relbarbgcolor": "#222222", "linkcolor": "#ff9632", "visitedlinkcolor": "#ff9632", "sidebarlinkcolor": "#ff9632", "relbarlinkcolor": "#a046fa", "sidebartextcolor": "#969696", "relbartextcolor": "#a046fa", "footerbgcolor": "#222222", "footertextcolor": "#969696", "headbgcolor": "#dddddd", "headtextcolor": "black", } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = "xpy_button.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = 'favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Expyrimentdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Expyriment.tex', u'Expyriment Documentation', u'Florian Krause \\& Oliver Lindemann', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'expyriment', u'Expyriment Documentation', [u'Florian Krause & Oliver Lindemann'], 1) ] python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Installation.Windows.rst0000664000175000017500000000316212304063646030013 0ustar oliveroliverPlatform-specific instructions: Windows ======================================= Dependencies ------------ If you are using Windows, download the following installers and follow their instructions: * `Python 2`_ * Pygame_ * PyOpenGL_ and, if needed: * PySerial_ * PyParallel_ and giveio_ * NumPy_ Installing Expyriment --------------------- To install the latest version of Expyriment, download "expyriment-|release|-win32.exe" from the `Release page`_ and execute it. Notes ----- **Do not start your experiments out of IDLE** If you are using the IDLE editor that comes with the Python installation, be aware that IDLE itself is written in Python. Starting your Expyriment programme out of IDLE (by clicking on "Run" or by pressing F5), might thus lead to improper timing! We therefore strongly suggest to run Expyriment programmes from the command line when testing participants. .. _`Python 2`: http://www.python.org/ftp/python/2.7.6/python-2.7.6.msi .. _Pygame: http://pygame.org/ftp/pygame-1.9.1.win32-py2.7.msi .. _PyOpenGL: https://pypi.python.org/packages/any/P/PyOpenGL/PyOpenGL-3.0.2.win32.exe .. _PySerial: http://sourceforge.net/projects/pyserial/files/pyserial/2.7/pyserial-2.7.win32.exe/download .. _PyParallel: http://sourceforge.net/projects/pyserial/files/pyparallel/0.2/pyparallel-0.2.win32.exe/download .. _giveio: http://sourceforge.net/projects/pyserial/files/pyparallel/giveio/giveio_setup.exe/download .. _NumPy: http://sourceforge.net/projects/numpy/files/NumPy/1.8.0/numpy-1.8.0-win32-superpack-python2.7.exe .. _`Release page`: http://github.com/expyriment/expyriment/releases/latest python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/timing_visual2.png0000664000175000017500000001662512304063646026641 0ustar oliveroliverPNG  IHDRZ\IDATx,)R,aKKKKH ߏqIp ^! ?;B6IM,D"9ΪZSaڿ$mȻLLtw}׈5aǺMDYwajɗ2TE);vVhڿuΆ}ui2D#.wDQv*C&f=ʞ 59 Gט杁S*_~Ms[8^FQK#cR{ _×s3d!ȍ#dn M=бF]"f93ޣ%MBnӀ;wrn*r~Kש1m:@{'3/h{>tھ tA\ ާ18X`niA}MkЩF@skRޣ쉻@(cxl@W3,\5M6B=ʞ 3@ݞL#5ʛGo,D],0^O7 G!"7*-ѾIc-!>.1lHL"եF6H$D S???u:(tra9t ͻ5k͗%g0-uk;Ne }L Ct,%k+neIt 3YaI}({c)8!]Gk*/Khw ݚ t_3 ND@/KBA+rޑW5~GKI7Vh_I[ quG:V.JV~~DЭP}# ½2D Э/;tt+h{@@W@7W!о Iؖn_&V$d 6 ?>_I ;D %/;ttsh{@@W@7*';tl6p1MBw$t7MσګĞAC]ګĞ  AJ ]ګĞ I6 ]`6 ` *'DAW@7*';ttsh{@@W@7*';tm7 0*ozeg[es9ngaZ\XI蕥eCf Z$nW=!v9W=AA+@@{9W=AA+ᓄ.fIBw$| *'DAW@7*';ttsh{@@W@7*';tl6MB3$tMBw3$8h)N|^etc, Hkd_eq+"y6؉@4H@Hnjh^ -ևtԊ]h&Zf@ jQ$ҚB\n0 kh-Q< @QS|ŪP/8۹vZDy6dt. ^^algȥHuLI hqa`HQR'Qd#YvT&. ۾^>,>v'hX"O:1DmK@4nչWʝx|$kUYDaZTNNiq*G:'xŠ`zRZ6dTܪR4P㩊lQɥl4M:HYyL>׍*>WҞ-E ^t}p*MYd=ءuUVG_ݱs+ndHַq<ӪꗖBz)ٙs]LІu)1 +Rh i5]Yg e;+imРDg>_Ԝ^o K}fr.Z#€qD(Ԩjޔ•%dhQ{.cu,M[z-Z nJj]JT!w[vU&\*k\(TZ εkJ|UI01WD?=i Z\} 8ۤtī#3zKq!X+zo olT#ӤJ>ktĽI4KCT6ՎBq׸azgo9$fXF0cF{Z=yr7Nq 6 `PhAaa7 ;``hhAA6 {`03,#&! =£JW1ci ̰ bFE 'nY?Zofanɢz5IsgڊDWz<5 tz%Ϗv k^ǽ1t:=+\Rqd ^.:_Ya:ǧgJpᢗˣ۸ Iu^TSk'Un  i tqI_k\ :^R4Euʎ9.`Np=iR5reQQVJD*VpL/̚g4c#qY5CK萶jk٪<`em+-5-(᷎22`jJHOŨb3ס=aZA_}P(xfv`6}>7gI}dEi*lٺݞ&QJY!<9.N,Ա-[|W2w @.*&b0S45S]R|xD]ϐ@_}b33F^Q= 0uN*< i_hԕ74~J@lva+(.F #zu60D#ᄁ*182 B8c.˺8i'-iىt{~F)+EGRF'F|L1k#&! f4sm$6c{ W٢7Z pje ⠍M?_+|Ј:Qn;]3Z6_aW 8<!-i_'ZW0 0 5u0@[ "-itЯvz~` vb?N$ ) >xU33$l;&^C3$6o6h6 ~K{¾: {5M1Q[CY !mx}xlhrݹZfFZ$쳾_E۟)nys6vlFyM–ȧ:_%a v.vze. qt#m&+< 7z^{m$YavqK|]_Y$m A1/5@ob@0趾~gxusѺYϲvnt â@g9^5Ix0Πi ^zoO$cL L3y{⵹5 k _{ ?Z?Ư~^mUZhͭq #SZc@̼FoSX+7R =iW/zF _\~8=8Ҵ  ~Wm}I~g z$-ҍ5Mv]$o9Kgm@qXIy`I-(ɵ%32hHr*rMZSU䌸^k#/)LSo!NϢx=PNO;GԗNGؓg*M$wYG/&gߺ$uTD9#-`GA.;OSo=v>:؊]z&ͮUNg%DϷֶ =ckWIÎ=H< U9wVGѓigHe'})fXMJDh~=`!iw;Z ϮwFwvT[=VIOFcMU@˖VJLC4[v/hQ؉S%9)f$nӏ@!G!_\PWmvb[Rmenyq@߲ 뻵QzN|5P+bk5K8"yOj,A7rd~:{Px\1foPY2TVvg)n|vJZݲ+^Y):o]wYܲINz ظIB ZYRgVTٵR@z}3,#7 X !`+#׍  0(4 o3,#&afXF0MBA`P$fXF0M̰`C@  0(4Ḭ`a 6 `PhAiI݇),EV,sk!Y9:'4 @  0(|a >I``hhAA6 {`03,#&! @  0(l3,#&afXF0MBA`P$fXF0M̰`C=L0̰`3"`3,aPy",,"kĬNI$~hD"K4D" }%r#>z ~/^_n|(@C}MGbv,"4G 1Vl~#АY |@OӪљq4Iko:r2zE3Md*,ltyet嚷0G*^nSEDkw* =F@OmӋ7 t7+hf_9^].ේ^m0/C/_sLR9N뵾@/NxQ)49F߶} t{*Bݤsl?AmzMXA$sjw@c{O}:ye)^~@Q {lPzg)S@'ELY#iM t:m]>>V@C9YDaFB]NYVjbm\zv L渝@7?.+E5 (rAmzSf>T [ tCa]bd}FhhGތ Wkk7ia<'3ejz[y@gyNg\n Y\/ X)k*@5Q]/v(dz@3M93 g6mF{`8w:vp\#aſE׷!I{kcq/6~Ҟh,\^Mz&Ayl%aƇ e ެ$d-A4":>Yw^^2xk9 Ы2#O~eIs;AC}>h ߢ#> /IENDB`python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Installation.OSX.rst0000664000175000017500000000331212304063646027027 0ustar oliveroliver.. _OSX: Platform-specific instructions: Mac OS X ======================================== Dependencies ------------ If you are using OS X, download the following installers and follow their instructions: * `Python 2`_ * Tcl_ * Pygame_ and, if needed: * NumPy_ * PyOpenGL_ and PySerial_ (has to be installed as described here_). Installing Expyriment --------------------- Download "expyriment-|release|.zip from the `Release page`_ and install as described here_. Notes ----- **Do not start your experiments out of IDLE** If you are using the IDLE editor that comes with the Python installation, be aware that IDLE itself is written in Python. Starting your Expyriment programme out of IDLE (by clicking on "Run" or by pressing F5), might thus lead to improper timing! We therefore strongly suggest to run Expyriment programmes from the command line when testing participants. .. _`Python 2`: http://python.org/ftp/python/2.7.6/python-2.7.6-macosx10.3.dmg .. _Tcl: http://www.activestate.com/activetcl/downloads/thank-you?dl=http://downloads.activestate.com/ActiveTcl/releases/8.4.19.6/ActiveTcl8.4.19.6.295590-macosx-universal-threaded.dmg .. _Pygame: http://pygame.org/ftp/pygame-1.9.1release-python.org-32bit-py2.7-macosx10.3.dmg .. _Numpy: http://sourceforge.net/projects/numpy/files/NumPy/1.8.0/numpy-1.8.0-py2.7-python.org-macosx10.3.dmg/download .. _PyOpenGL: http://pypi.python.org/packages/source/P/PyOpenGL/PyOpenGL-3.0.2.zip .. _PySerial: http://sourceforge.net/projects/pyserial/files/pyserial/2.7/pyserial-2.7.tar.gz/download .. _here: http://docs.python.org/install/index.html#the-new-standard-distutils .. _`Release page`: http://github.com/expyriment/expyriment/releases/latest python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/index.rst0000664000175000017500000000134312314530407025022 0ustar oliveroliver.. Expyriment documentation master file, created by sphinx-quickstart on Fri Apr 5 15:08:26 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Expyriment documentation ======================== .. image:: expyriment_logo.png *A Python library for cognitive and neuroscientific experiments*. :doc:`Read more ...` Contents -------- **About Expyriment** .. toctree:: :titlesonly: :maxdepth: 2 Overview API Reference Advanced Technical issues Changelog **Getting Started** .. toctree:: :titlesonly: :maxdepth: 2 Installation Beginner`s tutorial Example experiments python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Overview.rst0000664000175000017500000001127712314530407025530 0ustar oliveroliverOverview ======== **Expyriment** is an open-source and platform independent light-weight Python library for designing and conducting timing-critical behavioural and neuroimaging experiments. The major goal is to provide a well-structured Python library for a script-based experiment development with a high priority on the readability of the resulting programme code. It has been tested extensively under Linux and Windows. **Expyriment** is an all-in-one solution, as it handles the stimulus presentation, recording of I/O events, communication with other devices and the collection and preprocessing of data. It offers furthermore a hierarchical design structure, which allows an intuitive transition from the experimental design to a running programme. It is therefore also suited for students as well as experimental psychologists and neuroscientists with little programming experience. *Website*: http://www.expyriment.org *Authors* * `Florian Krause `_, Radboud University Nijmegen, The Netherlands * `Oliver Lindemann `_, University of Potsdam, Germany Main features ------------- * Easy syntax, very readable (due to Python) * Cross-platform (Linux, Windows, OS X) * Allows for fast and efficient programming of experiments (do more with less code) * A variety of standard stimuli (Text, Picture, Audio, Video etc.) * Keyboard, Mouse, Serial Port, Parallel Port and Gamepad support * Hierarchical structure of experimental units (Experiment, Blocks, Trials, Stimuli) .. image:: expyriment_structure.png For a full documentation of all Expyriment functionality (i.e. all available modules, classes, methods, functions, constants, and attributes provided by the Expyriment Python package), please have a look at the :doc:`API reference pages ` Example code ------------ Examples of full experiments can be found :doc:`here ` Creating experiments, blocks, trials and stimuli:: exp = expyriment.design.Experiment() block = expyriment.design.Block() trial = expyriment.design.Trial() stimulus = expyriment.stimuli.TextLine(text="Hello World") Building a hierarchy between various structures:: trial.add_stimulus(stimulus) block.add_trial(trial) exp.add_block(block) Presenting stimuli:: for block in exp.blocks: for trial in block.trials: trial.stimuli[0].present() Handling input devices:: button, rt = exp.keyboard.wait([expyriment.misc.constants.K_SPACE]) Logging data:: exp.data.add([button, rt]) Licence ------- Expyriment is free software and released under the Open Source `GNU General Public Licence `_ of the Free Software Foundation. Publications & Citation ----------------------- If you have used Expyriment in your work, please cite the follwing publication: Krause, F. & Lindemann, O. (2013). Expyriment: A Python library for cognitive and neuroscientific experiments. *Behavior Research Methods.* `doi:10.3758/s13428-013-0390-6 `_ .. FIXME update BRM publication details Development repository ---------------------- The `Expyriment development repository `_ is currently hosted on GitHub. Get a local copy of this repository with this command:: git clone https://github.com/expyriment/expyriment.git Mailing lists ------------- **Expyriment newletter**: All users of Expyriment should subscribe to the Expyriment newsletter, since the project is still under development. All modifications and new versions will be announce via this mailing list. (Visit `website `_ or send an email to expyriment+subscribe@googlegroups.com). **Expyriment users mailing list**: If you have questions regarding the installation, usage or documentation of Expyriment please don't hesitate to contact the Expyriment users mailing list (visit `website `_ or send an email to expyriment-users+subscribe@googlegroups.com) or contact us directly by sending an email to info@expyriment.org. Suggestions and bug tracking ---------------------------- If you want to make suggestions to improve Expyriment or you found a bug, please post your comments to the `Expyriment issues page `_ or contact us directly by sending an email to info@expyriment.org. Related Projects ---------------- If you are looking for a graphical experiment builder, we suggest OpenSesame, which uses Expyriment as the default back-end: http://www.osdoc.cogsci.nl/. python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Problems.rst0000664000175000017500000000245612304063646025511 0ustar oliveroliverCurrently known problems and limitations ======================================== Here are some current problems and limitations of Expyriment you should be aware of. Where possible, we include suggestions on how to deal with or work around the issue. MPEG-1 video playback only -------------------------- At the moment, only MPEG-1 video files can be played back. This is a limitation of the underlying video system of Pygame. On the long run, we are planning to move to a different Python video package. No native 3D stimuli -------------------- Right now Expyriment only offers static 2D visual stimuli. While PyOpenGL can be used direclty to create dynamic 3D stimuli, we are planning to add a dedicated 3D stimulus class in the future, to facilitate the creation of 3D stimuli. No support for multiple monitors -------------------------------- It is not possible to run Expyriment in fullscreen mode on a specific monitor, since the underlying Pygame package is not aware of multiple monitors. If the additional monitors are set to extend the desktop, then Expyriment will treat everything as one big display (spanned over all monitors). If you simply want to run an experiment on a different monitor (e.g. an external monitor on a laptop), we suggest to set the additional monitor to clone the primary one. python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/DataPreprocessing.rst0000664000175000017500000000100412314530407027322 0ustar oliveroliverData preprocessing ================== In most cases, data acquired by Expyriment needs to be further processed before a statistical analysis can be performed. This processing entails an aggregation of the dependent variables over all factor-level combinations of the experimental design. Expyriment provides an easy, but flexible way to automatize this process with the included data preprocessing module of the misc package (:doc:`expyriment.misc.data_preprocessing`). .. FIXME better docu data preprocessing python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/timing_audio1.png0000664000175000017500000001603712304063646026433 0ustar oliveroliverPNG  IHDRZIDATxF)!R.N o ` ` ` K BHɜq_377 &l J@ *:Õsa ~74ٰ; so͹z^ݫK{}ij/`hd?9%p lDfБ5% :eOfhj/xlw7dzھH3Ɇ뾚lnh&Φ>4}7俺3p# nh{_(l/РA]M +6 gOx#9-AtC32#ނGm{ͯb^ڳ4Agz4fۧM+vӌb '^vo0i֠i>"T9O'f}gWce_*g/-`ЄV;ﮄ@h$0 ܙ@a|>^@XV1hHQh1DXR:U1>KHX "ʲAo^hAn[BkcI-A/06 hM3 I#2 7]A-B-h0h,Z;>QcK 2d}p4h>8@` {C!.{C; 10N@ wn  ,XkƠ:> 4@w`g !*A v݁A ;0賀At}0hp Z!!*A v݁A ;0賀At}0hX0x<'j(Ea_D9JNvg4@w`g ,`݁A ;& ozdHZ!` Y> 4@w`g zdHZ!` Y> 4@w`g zdHZ!` Y#jг͠_`~=n ;fJ̣9$hAd$ 1_K>tivң1W6h{ 4@LZ`ghL+Z/J?$lAfQrpnW!QrFgM66 ;hY3=4@t;cmAt_> 4@ws[Ђ Ii`J!iA?7z.At}0h YC-hAdpHx.At}0h YC-hAdpHx.At}0h YC-hAdpHx.At}0h YcC9Cn%g}u(98Av݁A ;0賀At}0h[Ђ Ii`J!iA4@w`g ,`݁A ;8$܂dpHi`J!iA4@w`g ,`݁A ;8$܂dpHi`J!iA4@w`g ,`݁A ;V<$3f VA8I9 r!.Ѱ}j1[Px2FPFLf&+^2ϧ/U[Zmr1Ƙc^B]AK,ŭTvKtn ,,k̑ڵ .BuT297%6xhr7^6&%=,cfeh/Ȅ6v3h.[ry_/\66S^M"RbwfɧXbm{4[ Jb6Ndž_MSbgW %aЍ^ۂڂCKHLlx i3n z,zl ϸiZ KSʐw/d; `<;EϤt0̮cXsp;N|Y8 2T*rsX+pH&QDmЈBNe&aZT("(i젉:iB# :y9A C҈Fh5!iD9A  ( C҂ .J` 2~iAn (4@`! C҂  =!iAMA4  (4@pHȐ CBdHZ!!@`pVV+AI׍9_q=+DX-j >JΗZE''^t2Vr*npc+9J2s^׫M![ 2mnvq4[40hy>Eh^M{h0/:^Qw[ڜ"~wEzzպr2OvG$[0qbyN>K5 ڜ2-u3ڨX6JAłXE 3) bErcfd=TMl{Ɣ[z/8ѻ*  Cvf}m(.ƖtMEڷɵaA'wzwI8pdN2 `rZE/YXn*ܢ2|f[fJhǒRh}g{HoޗɒbSEcp8~wٜwJѱIG?/7XrWbTαX WמJc"\'.Y6)2uڠ,:6 Z):iк,ZJ,=;) 1D1h8 '%h7U\2|KOܺAW7#w}e)MU]B.on2$Ș 6Ӫy0D&~#(uֆdO6pIhł-e!(;h8I^gӉಸL3Za0ݠW-Myrtj`p88BaI^=Y۠HXX 1h8ӥY3cO7V.m)h8)[k&4 }ZԒtA&byrZϠ6IZ/I !ueMˬCBe3׋dpK3$ JK͢3 н[ECB8"3!| KA=/xРk-KSCmރsm ,GPB3^%cvm2;¶HPbpdjtu3[)ӋfBaE+ZvA6qĠX,ٲɹ#(Eee7Z=vǢEpHȐ~H| 7,tY$|FE.r IMcK]޿DZy>wל^[\X3 4dA־nw,lhW;bbp,FufTw[Ѡ+ [;%k Gc̠f̌=f+Gkvap4jгͭuqs EJlA38$AdCo os|1EO\2ƜVCB|Lvӿ r0yz\U Bb /PvnQSGmԢ[Y~ XM6T11mwSG_ql].L[5/ j1tmcђۤj3 ڽV=m -b.svmfn7s~\nn73fk^ 6[WroH'xsۆ<0Nbm[ NMD {]~* (ukUevܖN;.l v=x>-R?6^|k߾>IE~ɸw#9O/$yq׽̽zkłb񂭈WlIӦ׏N2[_d_<{t;GvcޱmYVMm6<'rF- ;v{v[=ڻ,l ߩVSk[^*w_6B wR(ť]~=x%w}񷡜O;T^ uw}K*tL(xȩrps;䬲~c}B_h:e *{eEֆ:eM7dKw*+&rP?6|>W-l0ꩶl6êš0-ll.6c-:$Tp85rtG?N&?ls>u-)Ki6kњ &A!!6sfA/q䒶c`w?u|v3;mXϪ*^tЪfmʊXf.Zy[;&#a/MB 2$e͍7 k=e.ôE+ϟ+M¹{EwCm&a*xAvɏ|n tbjҦSaֆ%8v쎉0~, :gXJ1ilݍ,a ^hJw$%s6s}S6M S ڬA]ҫ; ѠsA$ZkCA+k:2z+^t!IȐyH(4sk8[s_Y^Laӹm"J/&ѳE'7j,5VӐ0OB8Ky%v %EVw8UBN~nΩhF[M#x ):oTXc]2c2cp4j>- \|P-xj//&\lLLf^TQ:D1h7`*MhFXgw3#%mq.ZCC-@!f$qtE$eft/e sEg;BPO;%SG4SX!qH8E'!0p_N) MK[L ’El"\)VwLƠ^ɉ77Sd$9bJѮEQh1#ə&5t0݌̢]PkQ Y/p*mRV I!}AC`cOwL7IT?월UɇE'm6$;KYݱR*C-@spY]8BҌ :.|aME笗hbkw!!䴴'67#mUU$Gn7şm Ę\3*zwR뤰8AC$er>tɲ0.ZV&U%Yԅ)EDu^ac!!lJ&/U/˄TU'Yt=z{V,HPi 2sb&/|djS:EM-@]-d҂  h Q0hFp !iAȐ CBh Q8$dHZ!2$-i0hFh dpH I 28$ht +A/Ȑ C  z !Adx`J"(r`DEQ-F @ C}&z&F۠!v>T^x1PuS_}deD6HBCuݓb6N&4TĠ_dzs0|S A|^w^R5c⪼u/j:k؜ƟK*x ݠLQxlEȠ=]\iӷ{`I<8>mHi|im2hB z\T*5A+` pHb>2h9rr5źA[enJVsl?AF\tn#GR}lk/I{ ԩW7hv͏l Z^ ZI z$2U z %GK`n32h h=-XϠ3wӋtRX ސC6 :p3{Qk'7O\{2 c+ :'yXCtJ uEYp;$ߠ4.3C`bW2X5~2ۦ\W60hˮŊ;hV( E| NXqgvW=2KabGyl[y=mk$=4鞲%c]Zm"cH~Bt 35h\ Au3͹l;+e|w6t6L '?>O~( ]XRwkïhfBCuA}d(v#IENDB`python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/timing_visual1.png0000664000175000017500000001410212304063646026624 0ustar oliveroliverPNG  IHDRZ IDATxղFS,R,)-,(,a|Ǡ{,$! J$ xxpH 640cx ߚ #7COֆ|R{'`lw;#/ ^>jfC!_J6rwFv([{fö4Mܾ7:T]L2@3lֽl TSsO:N[ǫ|i26ԑ5Θܞ%e꜂ѱ"c"\'6))<' tF.61XeRڇ Yu\9k^zGs!rЮӀIm^v"Wva+6IDmuΏ3w$4Ccb;C6:iC=Hg n{йFE{Rֽl`@(mX t[ tQYw3^ƼZ-^6Xۺ +yLh\xdƬk!m! c V?Z~ ;on ŁFx3>v@<,"ЍSӑ$Jj?,xLC\Iҗ  B%5@')m"k5ij`R{XD1#!1R|X-N.? 4@t:n8 E{.D3 Ee-am8<` f0B@ ă{-hw. qΛW -a`7/*v3hŗ t?^@!4 @h7/7f<  a`O,2I0P 4@!4 `p 0  a`f `P 4@!.a7I"i$.I9A 0P 4@!4 kfOBfX""!4@!4 @hkfH K3X$0P 4@!4 s7"`%,:0òCY$0 u^c5>@$h Ugh64@ e%fЂfH ˶fHP4@!4 @hŲu xHZ!: $ruNf̠!4 @h 0@C('`%ЁfHa @h 0@C(hB"`%,:0  <̠!4 @h 0@C(X$\̰D0EBfX""!4@!4 x-{zϻ  @@CэEQmb7fP/mq)Pi.ydfyBje=*o4E\m5ݝׯvAfжvӺh̩m/3Z^v* )+uU˖_`~Q‹sŢmm-o\F-(6˚o[⚸iIkN^m\Hnd~1)bժUWr (-|,y>1].O!ڣ\! W vUOoaRnF)P#]uf%N%4NZ4cKw?.GrIk^ظ\p=fmMG9v{4)4ۭ7f!vRMu@ٕeHKWv#/'ų6X6gmcCcdmwCI]WT"FRCOcוtyMRQp:UIE"չ\v8yȮէ%c=ʎ$mom-sM*ʹh]^(k~:4s9uͰJMeSmmw99-S*J@*Pbܹ>O͹#-{?A]TOg[t:JFT \ҚV+sUՓDڊRxy;ԊZm/t$'m#l*ԽL׉ka"tCh@>".)~)οZ:JjәH!wD4ZQAV2 JrH&U:dmc֭~&px2M՞dX;ۗPXZiRSjW`Lj-'uӍ}Qb4}@Wo7jČorm6墓%9\#Ok3Ie[`%,nbt8q>.džlkw]^.tq^.n%Id1ېu t IR?Ȯw zFȥU!X*p*m}A$2IG$]7$i=\4_kGI!2*Hhl3 =5-ɩL-EPDZUu +UQ"Z_ S3dB#]F1A.L~>\ ~\ $D2JM9J QT]$"P(ݶFw:dBXvӸ:pH%qzߧtQt|LBP]3*JҁIã_ea I"iDuNfЯsdc dU߭M1 @+^&y#ʛs8Vux|S/"95C~>A/w`_Ⱥ.hfYwÇ⋌@׈óF"a,MT*6p?a3[!usfXیTz;ff5zYUZɦGj惹/ :?-q[q]~'Nfu}ՙxKm y;4 C[eVFӍq5xW:Kl nQ}CWdCd[V\Da'nMcK)FʣkQծX_/[]uW~-2_jfРowKiᶷJN+qZakXcuu%1:fׂs*Rifm-bdї>|~eV"$.%IΗZ0Α徙fqNp&>jfٕmmʶe靤1X: h~8NdUUX: &mmH[:> ٴuWvZkb۲oQqH7Xs>fz]@>1K.NXy.}?D7tvC^VۦtShV"4Ĉ\>"s2L(ύ+KsX[InufkI9,S$? G3 0 Zy4,,GM/ X<тfQfX"Տ?4@Ph AwbOL3X$t`%,   (,fX""3,` 44@Ph Aap 0  a`A@ʲu xHZ!:g-öI9A   (p 0 I K3X$   (4@PX$\̰D0EBfX""!@hh AA"`%,:0  B@   3,`ЁfH/#aŒg0Âp k̰`3tI"$HRhH"Ii9&a~&r&!=E6'.T.i }5i| FbvڗѰK] sҭL4@wFBE/}TxI-)f(n0i)Cܟ~Ӷ-FNC^Qsuce:Akn{3hg$YC'k(oM|eg;^]S|#;WQ3v24-v'й5@XL6A`v@MƴyNJM[U?h;4&ڶD$4/$emKy UGj 3R=>>:::;;;;;;;;;;;;;;;999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;;;;;;;;;;;;;;;;;;<<<===;;;;;;;;;<<<:::@@@GGGIIIIIIIIIJJJJJJJJJJJJJJJJJJJJJJJJJJJKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJKKKJJJJJJJJJJJJIIIIIIFFF@@@;;;<<<;;;;;;;;;;;;;;;===<<<<<<;;;GGGMMMLLLMMMMMMNNNNNNOOOOOOOOONNNOOOOOOPPPPPPQQQPPPQQQQQQQQQQQQQQQPPPQQQPPPPPPOOOOOOOOOOOOOOONNNNNNMMMMMMLLLMMMFFF;;;<<<<<<===;;;;;;===<<<<<>>===CCCLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKBBB===>>><<<;;;???>>>DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKBBB>>>>>>===<<>>DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKBBB>>>>>>>>><<<@@@???DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMKKKCCC???@@@???<<<@@@???DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOONNNNNNOOOMMMNNNNNNPPPPPPQQQRRRRRRRRRQQQPPPQQQPPPQQQMMMKKKKKKMMMNNNNNNMMMMMMLLLMMMMMMMMMLLLDDD???@@@???<<<@@@???DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOOOOOHHHIIIHHHFFFDDDQQQQQQRRRRRRRRRQQQPPPQQQQQQKKKKKKGGGHHH>>>MMMNNNMMMMMMLLLMMMMMMMMMLLLDDD??????@@@<<<@@@@@@DDDLLLMMMMMMMMMLLLMMMNNNMMMOOOOOOOOOJJJ[[[tttuuuhhh;;;PPPQQQRRRRRRRRRQQQPPPQQQQQQIIIwwwxxxsss666LLLOOOMMMMMMLLLMMMMMMMMMKKKCCC@@@@@@???===AAA@@@DDDLLLMMMMMMMMMMMMMMMNNNMMMOOOOOOOOOJJJ{{{SSSLLLRRRRRRRRRRRRQQQPPPPPPQQQQQQ\\\FFFNNNMMMMMMLLLMMMMMMMMMLLLDDD@@@AAA@@@===AAA@@@EEELLLFFFBBBBBBFFFLLLMMMMMMNNNJJJDDD@@@nnnooo>>>IIIIIIJJJKKKOOOQQQQQQPPPQQQjjj???LLLNNNMMMLLLMMMMMMMMMLLLDDD@@@@@@@@@>>>BBBAAACCCgggiiiGGGMMMMMM^^^bbb~~~zzzOOOEEEPPPPPPPPPTTT<<>>BBBAAAEEEUUU???KKKLLLNNNbbbGGGOOOOOONNNdddWWWCCCNNNLLLMMMMMMMMMLLLEEEAAABBBBBB>>>BBBAAAFFFIII^^^sss@@@MMMpppFFFMMMMMMPPPOOOMMM===JJJMMMLLLMMMMMMLLLEEEAAAAAAAAA>>>BBBBBBFFFLLLKKKhhh^^^UUU\\\KKKhhhiiipppFFFPPPOOOLLL888MMMMMMLLLMMMLLLFFFBBBBBBAAA???CCCBBBFFFLLLLLLMMMsssGGGOOORRR|||FFFMMMQQQ===OOOOOONNNWWWCCCMMMMMMMMMLLLFFFBBBBBBBBB???DDDCCCGGGLLLMMMLLLNNNvvvHHHPPPMMMMMMOOOOOOfffkkkHHHOOO]]]@@@KKKMMMMMMLLLFFFCCCCCCCCC@@@DDDCCCHHHLLLMMMMMMLLLMMMNNNNNNPPPNNN~~~QQQNNNOOOUUU<<>>IIIEEEFFFEEEAAAFFFEEEIIILLLMMMLLLOOODDD]]]BBBeeeXXXNNNOOOMMMIIIzzzAAAHHHEEEFFFFFFBBBGGGFFFIIILLLMMMMMMdddJJJSSSSSS}}}gggOOONNNMMMKKKiiioooEEEFFFGGGGGGBBBGGGFFFIIILLLMMMMMMNNNNNNKKKLLLKKKOOOOOOPPPMMMMMMMMMPPPRRROOONNNNNNOOOMMMOOOOOONNNKKKNNNSSSNNNMMMMMMKKKNNNNNNMMMMMMOOOLLLKKKLLLMMMIIIFFFGGGGGGBBBGGGFFFJJJMMMMMMLLLLLLLLLLLLMMMMMMOOONNNNNNNNNOOOOOOQQQPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPQQQPPPOOONNNNNNNNNNNNNNNMMMMMMLLLLLLLLLLLLLLLIIIFFFFFFFFFBBBHHHGGGJJJMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMLLLJJJGGGGGGFFFBBBHHHGGGKKKMMMMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMMMMJJJGGGGGGGGGCCCIIIHHHKKKMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMMMMKKKHHHIIIHHHCCCIIIHHHLLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMKKKIIIIIIHHHCCCJJJIIILLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMLLLIIIJJJIIICCCJJJIIILLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMLLLIIIJJJHHHCCCJJJIIILLLMMMLLLMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMLLLMMMLLLIIIIIIIIIDDDKKKJJJKKKMMMMMMMMMMMMLLLMMMNNNMMMOOOOOOOOONNNOOOOOOQQQQQQQQQPPPQQQRRRRRRRRRQQQPPPQQQQQQPPPOOOOOONNNOOOOOONNNMMMMMMLLLMMMMMMMMMNNNJJJJJJKKKJJJDDDKKKKKKJJJNNNMMMLLLLLLLLLMMMNNNMMMOOONNNNNNNNNOOOOOOQQQPPPPPPPPPQQQRRRQQQRRRQQQPPPPPPPPPPPPOOOOOONNNNNNNNNNNNMMMMMMLLLLLLLLLMMMNNNJJJKKKKKKIIICCCKKKLLLKKKKKKOOOMMMMMMMMMMMMMMMNNNOOOOOOOOOOOOOOOPPPQQQQQQQQQQQQQQQRRRRRRRRRQQQQQQQQQQQQQQQPPPOOOOOOOOOOOONNNNNNMMMMMMMMMMMMNNNJJJLLLLLLLLLFFFEEEtJJJLLLLLLLLLKKKNNNMMMNNNNNNNNNOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPPPPPPPPPPQQQPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNMMMNNNKKKLLLKKKLLLLLLCCCRRRDDDLLLLLLKKKLLLKKKLLLMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMLLLKKKLLLKKKLLLLLLJJJAAAA???8EEEKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLGGGKKKpVVVNNNEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEE@@@AAA0gggpython-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/create_rst_api_reference.py0000775000175000017500000001111612314530373030541 0ustar oliveroliver#!/usr/bin/env python """ make rst files for the expyriment API reference """ import inspect import expyriment def inspect_members(item): members = inspect.getmembers(eval(item)) modules = [] classes = [] methods = [] functions = [] attributes = [] for member in members: if member[0][0:1] != '_': if inspect.ismodule(member[1]): modules.append(member) elif inspect.isclass(member[1]): classes.append(member) elif inspect.isfunction(member[1]): functions.append(member) elif inspect.ismethod(member[1]): methods.append(member) else: attributes.append(member) return modules, classes, methods, functions, attributes def heading(txt, t="="): return txt + "\n" + len(txt)*t + "\n" def create_class_rst(class_name): with open(class_name + ".rst", 'w') as fl: fl.write(heading(class_name)) fl.write("\n.. autoclass:: " + class_name +"\n") fl.write(" :members:\n") fl.write(" :inherited-members:\n") fl.write("\n .. automethod:: " + class_name + ".__init__\n") def create_module_rst(mod_name, no_members=False): with open(mod_name + ".rst", 'w') as fl: fl.write(heading(mod_name)) fl.write("\n.. automodule:: " + mod_name + "\n") fl.write(" :members:\n") fl.write(" :undoc-members:\n") fl.write(" :show-inheritance:\n") fl.write(" :inherited-members:\n") modules, classes, methods, functions, attributes = inspect_members(mod_name) if len(attributes)>0: fl.write(heading("\nAttributes", "-")) for att in attributes: att = mod_name + "." + att[0] fl.write(".. py:data:: " + att + "\n\n") #t = eval("type(" + att + ")") v = eval("repr(" + att + ")") fl.write(" default value: {0}\n\n".format(v)) if len(modules)>0: fl.write(heading("\n\nModules", "-")) fl.write(".. toctree::\n :maxdepth: 1\n :titlesonly:\n") for m in modules: fl.write("\n " + mod_name + "." + m[0]) create_module_rst(mod_name + "." + m[0]) if len(classes)>0: fl.write(heading("\n\nClasses", "-")) fl.write(".. toctree::\n :titlesonly:\n") for cl in classes: fl.write("\n " + mod_name + "." + cl[0]) create_class_rst(mod_name + "." + cl[0]) if len(functions)>0: fl.write(heading("\n\nFunctions", "-")) for func in functions: fl.write(".. autofunction:: " + mod_name + "." + func[0] + "\n") fl.write("\n\n") #fl.write("\n\n.. "+repr(modules) + "\n") #fl.write(".. "+repr(classes) + "\n") #fl.write(".. "+repr(methods) + "\n") #fl.write(".. "+repr(functions) + "\n") #fl.write(".. "+repr(attributes) + "\n") def create_change_log_rst(): """create well shaped Changelog.rst from CHANGES.md""" changes_md = "../../CHANGES.md" changelog_rst = "Changelog.rst" fl = open(changes_md, 'r') out = open(changelog_rst, 'w') out.write("""Changelog ========== """) version_found = False for line in fl: if line.startswith("Version"): version_found = True if version_found: if line.startswith("New Feature") or line.startswith("Fixed") or\ line.startswith("Changes") or line.startswith("Changed"): out.write("\n" + line + "\n") # additonal blanklines elif line.startswith("--"): out.write(line + "\n") elif line.startswith(" - "): out.write("\n" + line) else: out.write(line) out.close() fl.close() # main module with open("expyriment.rst", 'w') as fl: fl.write(""" expyriment ========== .. automodule:: expyriment Packages -------- .. toctree:: :maxdepth: 1 :titlesonly: expyriment.control expyriment.design expyriment.io expyriment.misc expyriment.stimuli Functions --------- .. autofunction:: expyriment.get_version .. autofunction:: expyriment.show_documentation .. autofunction:: expyriment.get_system_info .. autofunction:: expyriment.get_experiment_secure_hash """) sub_modules = ["expyriment.io", "expyriment.design", "expyriment.stimuli", "expyriment.control", "expyriment.misc"] #sub_modules for mod_name in sub_modules: create_module_rst(mod_name) create_change_log_rst() python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Unicode.rst0000664000175000017500000000357012304063646025312 0ustar oliveroliverUsing non-English characters ============================ Expyriment has full `unicode support `_. This means that, in principle, non-English characters (such as umlaut, accent, special character) can be used in strings throughout the library. Two different forms of using non-English characters have to be dissociated:: Non-English characters in strings in the Expyriment script file --------------------------------------------------------------- When attempting to use non-English characters in strings in your Expyriment script file, the following three conditions have to be met: 1. **Only use non-English charactes in unicode strings!** For example: Use ``u"Überexperiment"`` instead of ``"Überexperiment"``. 2. **Know the encoding used by your editor!** For example: IDLE will automatically suggest to save in utf-8 encoding when non-English characters are found in the script file. We suggest to always save in utf-8. 3. **Define the encoding in your Expyriment script file!**:: # -*- coding: -*- The line has to be one of the first two lines in the file, where is the encoding used! For example:: # -*- coding: utf-8 -*- Non-English characters in other text files (e.g. stimuli lists) --------------------------------------------------------------- When an Expyriment method saves a text file, it will always automatically add a header line specifying the encoding with which the file was saved. Which encoding this is depends on the system Expyriment is running on (it uses the default encoding defined by the locale settings). When an Expyriment method reads in a text file, it will always read the header line first and decode the text automatically (into unicode strings). If no such header is found, the encoding set by the system locale will be used (and if this fails, utf-8). python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/ImportDataIntoR.rst0000664000175000017500000000051712304063646026742 0ustar oliveroliverImport Expyriment data into R ============================= The function expyriment_data.R_ concatenates all data and returns an R data frame with all subjects. Between subject factors will be added as variables to the data matrix. .. _expyriment_data.R: https://raw2.github.com/expyriment/expyriment-tools/master/expyriment_data.R python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/timing_audio2.png0000664000175000017500000001570412304063646026434 0ustar oliveroliverPNG  IHDRZIDATxF)!,ab o _ ),,W,ȅ̜#c uD7bccccp{3\i)Ơof?-d-1Nsͯ-9z^Ëv(_ɏ6]]Fd"Q YSƠ;Yvh֮[sgX]5#]7rׯˋ4.VwC * M M1w6Q8<5uߐu&^۝2hAGfh{ۿضN :7u05CV#la.8|ʑGIMaГfhCuKנi2U,»^#gЅAg0iӠ/‚auը dgٗ'2h}/+Q@,!J}4#{īTqp34>2COP,»$|Rܗ9*v(rW!<(|Ql·+ackdc05llr[5NjmVŠSB.w$kA>KjV 4@wH-8< tQ 2$-`\0 ,`݁A ;0賀At{Ђ  = tQ 2$-`\0 ,`݁A ;0賀AtGE:PH؂BsN8V!3dd}At}0h YoA 2&.*A, f݁A ;0賀At}0h`pZ".*A, f݁A ;0賀At}0h`pZ".*A, f݁A ;= }0h *ԣ1cAGR"G 2X$4E%Ȑ #Hh|-hf  n6A7 >ޜxca A4N> 4@D#>nW [P[Hv *$tΐ3R94h"a0hZ8cvG 40h.XhЅA  |,`ύA 2&.*A` Y> 4@w`g ,zdHZ"` Y> 4@w`g ,zdHZ"` Y> 4@w`g ,zdHZ"` Y> 4@w`g莺u 4@w`g ,`" EBO]T I 2X$<̠> 4@w`g ,`" EBO]T I 2X$<̠> 4@w`g ,`Qqp.+m,}d ORي:\F#e2xImgv[ ׫1<JsRgEZW+.+D? )ଲl. JO$zjq+etBwumMÞsܾ%"Ɇ7(BxrUx, #^Ѣ L Pdconmݽ Oő/ь*f< +/^#VeȪ%wZjGD}eRK=VjXWLen1Rce$όֹhCD[ܖO|a^D-5:=gðc%ڟg,,E 4SV2%(0 m$UT1W6MIm4JV On!F:y&aZ!B IdM!F:y砗AHm C҈  /4@s{ I 2R+؇(9Z1Ș҂  Q0hFE=@,z C҂  h Q0hFap!iAȐ EBH/lT9!%&e${Y #;h`^vʵJt}IYv$O;E¬eCY$dHZ_$^ C^E# *a Ue0=cߊ_.f-r#y0d YUDFz {i.mA <݌8jY_ya% tΡg'ƮWaK_*urfjlMsnj4@ "5xԨo;52Ō;tF3ʖZ-*)Y!E5n {}uAİ>rk%}4#%q+,5ԍCYu[t=q]lcjVdm`)U|PnFǼsI}f[U¬t6Oϳ7-c2w\fisoǺueeBsNHyI$1xRW}KN=+(7/ ED+!Y]ȁAeekCiaH6.r m̠b,edrf C҂7 uvΫkvD;4%5bJ3*亨^'a_5eV"!@ sòl#̹s$u8q%?nG)u74@-7󓒨r4ure]N0h7c^ժS݌Sg ZVQ .0kwv ܠ.ɱZouezwݠU-pIU\2uVoh:"n C҂"at"低̠ Z~(k%Z>:Y$ )NX$#,A=ZJ֗ժfT8QMeX1Ҡ7N-#;fD zuss`9ݢ5֣NG$\̠Rό<Vh5VҶwia֟#,2$-XHڜ& Yq<M yY'$H2k@ f > Y8(~d0km+CyEmx(֪z!QVXkc{:E##ۺԾjsԩ `ayAѹYfs_n ^X'm"2$- òykKN4xV_[ RJܢN&a4|3}Y? iL=;Oոh*%0S^DgE.O'{|)GSV u=+. xmn9f9 3{t]XZ' ΚCGdxl:yVj\f (ZRxX0coZ͑PR9fW=z|bWsߵ{z^ߡ} }ĞlC25r;rߞ`_|o73 l&uK9L>yf/'At]0ʑl qWPvG֌@AfR{a;.߿-kKֶLivvEvxL͞펕w.Sɞ>rj#樓G۠_/|n>?זx_/?:9ˤ](]4e^ -"%ǫY.)(ZvOIN<1JDGrGFv3ǤJde^kk/3CE+gaYV7> |E/tysI]:u,ra;7:2$-nAˁS_#9Vڻ4ZL3Nta:qqcХ̠Yv#Q7 ̨p5 diZcX'.<5 ^iU-rx:Y֣?)JGPY/a^4a dD4"a49C΂aQuU֣”:*e4\bX$dHZ\$53pKuP/0uTa"a:FEBN_+ɺ#De]9̧2(v4]6,~g#V/kiJteYoj  7rxNL-XFV$X+l6eOW:)zKi5u#HRnF͊)[=Gp9n٬&͡<&.if% 0nj e 'ﺰYR:Uj`'%䱏CɺРSow`%B VN5GN`p!iAFdpAK~`ʌtG(7!(VK7llY}yTu\$4\e@DO`A}>lX\>[LEAsIwBLyhޖR :-| g-9;AcFZZS Y [uJ_S*ubV7]rH9\MTSsIljXq7nFkW +*5;!5> [|oXՅH1hc1k-cR6/Z gЩk%%E[>M%oުSQw0T"CHv:glD8B\ɗ ZW *mWNh9 ZfQϠ gN :+FO(|ġ^wZ*g1ם1V89<{p!)4hoT2˳WUJW|!w/S=syA{tGM :j ci1)#>Ĉ^ؠ~%NUѲ&)k#_ʝh:m<Ϡur'Xq r:쎸92]M!ܩ!Y\/TP=eg)^pӭADR݂gsÄ Q?f0Qoxʴ}ĠM>Or(qgA{ jϠ{|cI7ʠ%u+|P~E* :m#+b?zIENDB`python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Makefile0000664000175000017500000001124012314530373024620 0ustar oliveroliver# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR) rm -f expyriment.* rm -f Changelog.rst rst: @make expyriment.rst expyriment.rst: ln -fs ../../expyriment/ ./ ./create_rst_api_reference.py rm -f expyriment html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Expyriment.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Expyriment.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Expyriment" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Expyriment" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Installation.rst0000664000175000017500000000250712304063646026364 0ustar oliveroliverInstallation ============ How to install Expyriment? -------------------------- The latest releases of Expyriment can be downloaded from GitHub_. Note, that Expyriment depends on the following software packages that have to be installed on your system: * `Python 2`_ (>=2.6), * Pygame_ (>=1.9) * PyOpenGL_ (>=3.0). Additional packages, which are optional and only required for some features of Expyriment are PySerial_ (>=2.5) (to use serial port communication), PyParallel_ (>=0.2) (to use parallel port communication) and NumPy_ (>=1.6) (to use data preprocessing). Please be aware that Expyriment plugins (extras) might have additional dependencies. **Importantly, Expyriment relies on 32-bit versions of all these packages!** We provide more detailed platform-specific instructions for installing Expyriment here: .. toctree:: :maxdepth: 1 :titlesonly: Windows Linux Mac OS X Android .. _`Python 2`: http://www.python.org/ .. _Pygame: http://www.pygame.org/ .. _PyOpenGl: http://www.pyopengl.sourceforge.net .. _PyParallel: http://pyserial.sourceforge.net .. _PySerial: http://pyserial.sourceforge.net/pyparallel.html .. _NumPy: http://numpy.org/ .. _GitHub: https://github.com/expyriment/expyriment/releases python-expyriment-0.7.0+git34-g55a4e7e/documentation/sphinx/Examples.rst0000664000175000017500000000342612304063646025502 0ustar oliveroliverExample Experiments =================== Here you can find some code examples to see Expyriment in action. All examples are fully working experiments. Simon task ----------- An experiment to asses a spatial stimulus-response compatibility effect (see `wikipedia `_). .. literalinclude:: ../../examples/simon_task.py Word fragment completion task ----------------------------- Task as used for instance in `Weldon, 1991 `_. The script read in a stimulus list file (:download:`demo stimulus list <../../examples/word_fragment_completion_stimuluslist.csv>`). .. literalinclude:: ../../examples/word_fragment_completion_task.py Number classification task -------------------------- A full experiment to access SNARC and SNARC-like effects in a number and a letter classification task (e.g., `Gevers, Reynvoer, & Fias (2003) `_) with two response mappings, error feedback and between-subject factors. .. literalinclude:: ../../examples/snarc_experiment.py Line bisection task ------------------- Example of a line bisection task that is optimized for the use of touchscreens and the `Expyriment Android Runtime`_. .. literalinclude:: ../../examples/line-bisection.py .. _`Expyriment Android Runtime`: https://github.com/expyriment/expyriment-android-runtime/ Really short example -------------------- Expyriment is efficient!. See here a very short example of an functioning experiment in less than 20 lines of pure code. .. literalinclude:: ../../examples/really_short_exp.py Data preprocessing ------------------ Preprocessing the data of the SNARC experiment for further statistical analysis. .. literalinclude:: ../../examples/snarc_data_preprocessing.py python-expyriment-0.7.0+git34-g55a4e7e/examples/0000775000175000017500000000000012314530407020614 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/examples/word_fragment_completion_stimuluslist.csv0000664000175000017500000000204612304063646031270 0ustar oliveroliverambulance,a__ula_c_ arrow,_r__w ashtray,__ht_ay balloon,__l_oo_ bicycle,__c__le cactus,_ac_u_ camera,_ame_a canoe,___oe carrot,c___ot diamond,_ia__nd elephant,_lep__n_ envelope,_nve___e escalator,_sca__t__ fireplace,_ir_p_a_e flower,f__we_ football,_oo__al_ giraffe,__r_f_e grasshopper,_ra_s_o_p__ helicopter,_e_ico___r igloo,i_lo_ intestines,in__stin_s kangaroo,_a_g_r_o lobster,l_bs___ microscope,_i_ros_op_ motorcycle,_oto__y___ mountain,m_un__in needle,n__d_e newspaper,_ew__ape_ octopus,__topu_ ostrich,_s_ri_h parachute,_ar_ch_t_ peanut,_e_nu_ pencil,_e_c_l penguin,pe__ui_ piano,_i__o pyramid,_yr_mi_ refrigerator,_efri___ato_ rhinoceros,__in_ce_os sandwich,s__d_i_h saxophone,sa_op_on_ scissors,__isso__ screwdriver,s__ew_ri_e_ skunk,_ku_k snowman,_no__an squirrel,s_ui_re_ submarine,__bm_ri_e suitcase,__itc_se sweater,_w_ate_ telescope,__le_c_pe television,_e_ev__i_n thermometer,_her__m_te_ toaster,to_s__r toothbrush,to_t__rus_ tornado,_orn_d_ turkey,t_r__y typewriter,t__e_rit__ umbrella,_m_re_l_ unicorn,_n_cor_ violin,_io__n watermelon,_ate__elo_ python-expyriment-0.7.0+git34-g55a4e7e/examples/snarc_experiment.py0000664000175000017500000001052312304063646024542 0ustar oliveroliver#!/usr/bin/env python # -*- coding: utf-8 -*- """ A parity judgment task to assess the SNARC effect. See e.g.: Gevers, W., Reynvoet, B., & Fias, W. (2003). The mental representation of ordinal sequences is spatially organized. Cognition, 87(3), B87-95. """ from expyriment import design, control, stimuli from expyriment.misc import constants control.set_develop_mode(False) ########### DESIGN #################### exp = design.Experiment(name="SNARC") # Design: 2 response mappings x 8 stimuli x 10 repetitions for response_mapping in ["left_odd", "right_odd"]: block = design.Block() block.set_factor("mapping", response_mapping) #add trials to block for digit in [1, 2, 3, 4, 6, 7, 8, 9]: trial = design.Trial() trial.set_factor("digit", digit) block.add_trial(trial, copies=10) block.shuffle_trials() exp.add_block(block) exp.add_experiment_info("This a just a SNARC experiment.") #add between subject factors exp.add_bws_factor('mapping_order', ['left_odd_first', 'right_odd_first']) #prepare data output exp.data_variable_names = ["block", "mapping", "trial", "digit", "ISI", "btn", "RT", "error"] #set further variables t_fixcross = 500 min_max_ISI = [200, 750] # [min, max] inter_stimulus interval ITI = 1000 t_error_screen = 1000 no_training_trials = 10 ######### INITIALIZE ############## control.initialize(exp) # Prepare and preload some stimuli blankscreen = stimuli.BlankScreen() blankscreen.preload() fixcross = stimuli.FixCross() fixcross.preload() error_beep = stimuli.Tone(duration=200, frequency=2000) error_beep.preload() #define a trial def run_trial(cnt, trial): # present Fixation cross and prepare trial in the meantime fixcross.present() exp.clock.reset_stopwatch() ISI = design.randomize.rand_int(min_max_ISI[0], min_max_ISI[1]) digit = trial.get_factor("digit") target = stimuli.TextLine(text=str(digit), text_size=60) target.preload() exp.clock.wait(t_fixcross - exp.clock.stopwatch_time) #present blankscreen for a random interval blankscreen.present() exp.clock.wait(ISI) # Present target & record button response target.present() btn, rt = exp.keyboard.wait([constants.K_LEFT, constants.K_RIGHT]) #Error feedback if required if block.get_factor("mapping") == "left_odd": error = (digit % 2 == 0 and btn == constants.K_LEFT) or \ (digit % 2 == 1 and btn == constants.K_RIGHT) else: error = (digit % 2 == 1 and btn == constants.K_LEFT) or \ (digit % 2 == 0 and btn == constants.K_RIGHT) if error: error_beep.present() #write data and clean up while inter-trial-interval blankscreen.present() exp.clock.reset_stopwatch() exp.data.add([block.id, block.get_factor("mapping"), cnt, target.text, ISI, btn, rt, int(error)]) exp.data.save() target.unload() exp.clock.wait(ITI - exp.clock.stopwatch_time) ######### START ############## control.start(exp) # permute block order across subjects if exp.get_permuted_bws_factor_condition('mapping_order') == "right_odd_first": exp.swap_blocks(0, 1) # Run the actual experiment for block in exp.blocks: # Show instruction screen if block.get_factor("mapping") == "left_odd": instruction = "Press LEFT arrow key for ODD\n" + \ "and RIGHT arrow key for EVEN numbers." else: instruction = "Press RIGHT arrow key for ODD\n" + \ "and LEFT arrow key for EVEN numbers." stimuli.TextScreen("Indicate the parity of the numbers", instruction + "\n\nPress space bar to start training.").present() exp.keyboard.wait(constants.K_SPACE) #training trials for cnt in range(0, no_training_trials): trial = block.get_random_trial() run_trial(-1 * (1 + cnt), trial) #training trails has negative trial numbers # Show instruction screen stimuli.TextScreen("Attention!", instruction + "\n\nThe experimental block starts now.").present() exp.keyboard.wait(constants.K_SPACE) # experimental trials for cnt, trial in enumerate(block.trials): run_trial(cnt, trial) ####### END EXPERIMENT ######## control.end(goodbye_text="Thank you very much for participating in our experiment", goodbye_delay=5000) python-expyriment-0.7.0+git34-g55a4e7e/examples/really_short_exp.py0000775000175000017500000000206712304063646024566 0ustar oliveroliver#!/usr/bin/env python # -*- coding: utf-8 -*- """ A very short example experiment in 16 lines of pure code. Participants have to indicate the parity of digits by pressing the left arrow key for odd and the right arrow key for even numbers. """ from expyriment import control, stimuli, design, misc digit_list = [1, 2, 3, 4, 6, 7, 8, 9]*12 design.randomize.shuffle_list(digit_list) exp = control.initialize() exp.data_variable_names = ["digit", "btn", "rt", "error"] control.start(exp) for digit in digit_list: target = stimuli.TextLine(text=str(digit), text_size=80) exp.clock.wait(500 - stimuli.FixCross().present() - target.preload()) target.present() button, rt = exp.keyboard.wait([misc.constants.K_LEFT, misc.constants.K_RIGHT]) error = (button == misc.constants.K_LEFT) == digit%2 if error: stimuli.Tone(duration=200, frequency=2000).play() exp.data.add([digit, button, rt, int(error)]) exp.clock.wait(1000 - stimuli.BlankScreen().present() - target.unload()) control.end(goodbye_text="Thank you very much...", goodbye_delay=2000) python-expyriment-0.7.0+git34-g55a4e7e/examples/word_fragment_completion_task.py0000664000175000017500000000371612304063646027313 0ustar oliveroliver#!/usr/bin/env python # -*- coding: utf-8 -*- """ Word fragment completion task as used in the study of Weldon (1991). Stimulus list: "word_fragment_completion_stimuluslist.csv"! Weldon, M. S. (1991). Mechanisms underlying priming on perceptual tests. Journal of Experimental Psychology: Learning, Memory, and Cognition, 17, 526-541. """ import csv from expyriment import design, control, stimuli, io, misc control.set_develop_mode(True) #### read in wordlist file and make design exp = design.Experiment("word fragment completion test") block = design.Block() with open("word_fragment_completion_stimuluslist.csv", "rb") as f: reader = csv.reader(f) for row in reader: trial = design.Trial() trial.set_factor("word", row[0].strip()) trial.set_factor("fragment", row[1].strip()) block.add_trial(trial) block.shuffle_trials() exp.add_block(block) exp.add_data_variable_names(["word", "fragment", "RT", "RT2", "answer"]) control.initialize(exp) #prepare some stimuli fixcross = stimuli.FixCross(line_width=1) fixcross.preload() blank = stimuli.BlankScreen() blank.preload() txt_input = io.TextInput("") control.start(exp) #run experiment for trial in exp.blocks[0].trials: #present blank inter-trial-screen and prepare stimulus blank.present() fragment = "" for c in trial.get_factor("fragment").upper(): fragment += c + " " target = stimuli.TextLine(fragment.strip()) target.preload() exp.clock.wait(1000) #present fixcross fixcross.present() exp.clock.wait(500) #present target target.present() key, rt = exp.keyboard.wait(misc.constants.K_SPACE) #ask response exp.clock.reset_stopwatch() answer = txt_input.get() rt2 = exp.clock.stopwatch_time #process answer and save data blank.present() answer = answer.strip() exp.data.add([trial.get_factor("word"), trial.get_factor("fragment"), rt, rt2, answer]) target.unload() control.end() python-expyriment-0.7.0+git34-g55a4e7e/examples/simon_task.py0000664000175000017500000000336512314530407023344 0ustar oliveroliver#!/usr/bin/env python # -*- coding: utf-8 -*- """ A simple behavioural task to asses a Simon effect. See also: http://en.wikipedia.org/wiki/Simon_effect """ from expyriment import design, control, stimuli, io, misc # Create and initialize an Experiment exp = design.Experiment("Simon Task") control.initialize(exp) # Define and preload standard stimuli fixcross = stimuli.FixCross() fixcross.preload() # Create IO #response_device = io.EventButtonBox(io.SerialPort("/dev/ttyS1")) response_device = exp.keyboard # Create design for task in ["left key for green", "left key for red"]: b = design.Block() b.set_factor("Response", task) for where in [["left", -300], ["right", 300]]: for what in [["red", misc.constants.C_RED], ["green", misc.constants.C_GREEN]]: t = design.Trial() t.set_factor("Position", where[0]) t.set_factor("Colour", what[0]) s = stimuli.Rectangle([50, 50], position=[where[1], 0], colour=what[1]) t.add_stimulus(s) b.add_trial(t, copies=20) b.shuffle_trials() exp.add_block(b) exp.add_bws_factor("ResponseMapping", [1, 2]) exp.data_variable_names = ["Position", "Button", "RT"] # Start Experiment control.start() exp.permute_blocks(misc.constants.P_BALANCED_LATIN_SQUARE) for block in exp.blocks: stimuli.TextScreen("Instructions", block.get_factor("Response")).present() response_device.wait() for trial in block.trials: fixcross.present() exp.clock.wait(1000 - trial.stimuli[0].preload()) trial.stimuli[0].present() button, rt = response_device.wait() exp.data.add([trial.get_factor("Position"), button, rt]) # End Experiment control.end() python-expyriment-0.7.0+git34-g55a4e7e/examples/line-bisection.py0000664000175000017500000000554212304063646024105 0ustar oliveroliver#!/usr/bin/env python # -*- coding: utf-8 -*- """ A line bisection task. This example is appropriate to illustrates the use of the Android runtime environment for Exypriment on tablet PC. """ from expyriment import control, stimuli, io, design, misc # settings design.defaults.experiment_background_colour = misc.constants.C_GREY design.defaults.experiment_foreground_colour = misc.constants.C_BLACK line_length = 200 def line_bisection_task(line_length, position): # make button button = stimuli.Rectangle(size=(40,20), position=(exp.screen.size[0]/2-25, 15-exp.screen.size[1]/2)) button_text = stimuli.TextLine(text="ok", position=button.position, text_colour=misc.constants.C_WHITE) mark_position = None while True: canvas = stimuli.BlankScreen() line = stimuli.Rectangle(size=(line_length,3), position=position, colour=misc.constants.C_BLACK) line.plot(canvas) if mark_position is not None: # plot button and mark line on canvas button.plot(canvas) button_text.plot(canvas) markline = stimuli.Rectangle(size=(1,25), position=(mark_position, line.position[1]), colour=misc.constants.C_RED) markline.plot(canvas) # present stimulus canvas.present() # wait for mouse or touch screen response _id, pos, _rt = exp.mouse.wait_press() # process clicked position position if abs(pos[1]-line.position[1])<=50 and\ abs(pos[0]-line.position[0])<=line_length/2: mark_position = pos[0] else: if button.overlapping_with_position(pos): # is button pressed return mark_position - line.position[0] ### init ### exp = control.initialize() # create touch button box buttonA = stimuli.Rectangle(size=(80, 40), position=(-60, 0)) buttonB = stimuli.Rectangle(size=(80, 40), position=(60, 0)) textA = stimuli.TextLine(text="quit", position=buttonA.position, text_colour=misc.constants.C_WHITE) textB = stimuli.TextLine(text="next", position=buttonB.position, text_colour=misc.constants.C_WHITE) touchButtonBox = io.TouchScreenButtonBox(button_fields=[buttonA, buttonB], stimuli=[textA, textB]) ### start ### control.start(exp) exp.mouse.show_cursor() # trial loop while True: # find random position rx, ry = ((exp.screen.size[0]-line_length)/2, (exp.screen.size[1]-50)/2) pos = [design.randomize.rand_int(-rx, rx), design.randomize.rand_int(-ry, ry)] # present task judgment = line_bisection_task(line_length, position=pos) # save data exp.data.add(pos +[judgment]) # ask for new trail touchButtonBox.show() btn, _rt = touchButtonBox.wait() if btn==buttonA: break ## end## control.end() python-expyriment-0.7.0+git34-g55a4e7e/examples/snarc_data_preprocessing.py0000664000175000017500000000203612304063646026236 0ustar oliveroliver#!/usr/bin/env python # -*- coding: utf-8 -*- """ Example analysis script for snarc_experiment.py The current script produces two files for different analysis of the SNARC effect (ANOVA vs. slopes analysis) using mean and median RTs """ from expyriment.misc import data_preprocessing, constants agg = data_preprocessing.Aggregator(data_folder="./data/", file_name="snarc_experiment") agg.set_subject_variables(["mapping_order"]) agg.set_computed_variables(["parity = digit % 2", #0:odd, 1:even "size = digit > 5", #0:small, 1:large "space = btn == {0}".format(constants.K_RIGHT) #0:left, 1:right ]) # RTs: space x size agg.set_exclusions(["RT > 1000", "RT < 200", "error == 1", "trial<0"]) agg.set_dependent_variables(["mean(RT)"]) agg.set_independent_variables(["size", "space"]) print agg agg.aggregate(output_file="rt_size_space.csv") # RTs: slopes analysis agg.set_independent_variables(["digit"]) agg.aggregate(output_file="rt_digits.csv") python-expyriment-0.7.0+git34-g55a4e7e/CHANGES.md0000664000175000017500000003747012314530407020403 0ustar oliveroliver======================== Expyriment Release Notes ======================== Version 0.7.0 (2 Mar 2014) -------------------------- New Features: - new feature in testsuite: Font viewer - new extra stimulus: stimuli.extras.RandomDotKinematogram - new timer and experiment clock to ensure monotonic timing - Clock: new method (static) monotonic_time (this time should be always used) - data_preprocessing: new exclusion rule, which allows removing trials depending on their deviation (std) from mean (e.g., 'RT > 1.5*std') - improvements for OS X in get_system_info() - proper unicode handling: use unicode strings whenever unicode characters are needed - files: the character encoding is now written to files and used when opening them - FreeFonts are now part of the Expyriment distribution to guarantee the same fonts across platforms - new io class: TouchScreenButtonBox - new options for control.start(): skip_ready_screen and subject_id to start with predefined subject id - experiments now also have a global mouse object: experiment.mouse - new property for io.Mouse: is_visible - Secure hashes for experiments help to ensure that the correct version is running in the lab. Secure hashes will be displayed at the start and printed in all output files as well as in the command line output. Fixed: - experiment clock now with monotonic timing - bug in extras.CedrusResponseDevice - several bugs in documentation - incompatibility with multiprocessing.Pool - bug in Visual.add_noise() - bug in io.SerialPort.read_line() - bugfix: stimuli.shapes can now be used as background stimuli for io.TextInput & io.TextMenu Changed: - several Android related changes (have no impact for normal use of Expyriment) - overlapping methods of stimuli now work on absolute_position by default Version 0.6.4 (5 Aug 2013) -------------------------- New Features: - log levels can be changed while running experiment via the method Experiment.set_logging. Access current via Experiment.loglevel - Modification the logging of individual objects while runtime. Most classes have now the method set_logging(onoff), to switch on or off the logging. - design.randomize.rand_element returns a random element from a list - blocks and trails have the property 'factor_dict', which is a dictionary with with all factors - experimental Android support Fixed: - incorrect background colour for TextInput - Font in TextScreen - several fixed in documentation - switching off logging via "expyriment.control.defaults.event_logging = 0" not now working - "numpy version bug" in data.preprocessing - unicode bug for picture, audio and video filenames - polling of parallel port - io.TextMenu font was hardcoded Version 0.6.3 (14 Apr 2013) --------------------------- New Features: - misc.geometry contains function to convert between pixel and degrees of visual angle: position2visual_angle & visual_angle2position - io.TextInput has now a position and can be plotted on a background stimulus - misc.find_font - misc.list_fonts Fixed: - Initializing experiments in the IDLE shell - TextInput user_text_font and user_text_bold can now be changed - bugs in font selection - API reference tool should now also open when there are whitespaces in Python executable path Changed: - renamed TextInput.user_colour --> user_text_colour - FixCross.cross_size has been renamed to FixCross.size. FixCross.size is now a tuple (int, int) and defines both dimensions (x, y) separately. - Expyriment is checking also the version of all required packages and dependencies - all doc string are now in line with the numpy-doc conventions Version 0.6.2 (12 Dec 2012) --------------------------- New Features: - new stimuli.extras.PolygonLine class Fixed: - Expyriment could not be imported on Windows 7 64 - misc.geometry.position2coordinate bug - io.Mouse.self_test bug is fixed Changed: - stimuli.Line was rewritten to not depend on PolygonRectangle anymore; the old Line stimulus is now stimuli.extras.PolygonLine Version 0.6.1 (9 Dec 2012) -------------------------- Fixed: - Testsuite wouldn't start anymore - API reference tool would not start on Windows XP in some cases Version 0.6.0 (4 Dec 2012) -------------------------- New Features: - new stimuli.Circle class - new stimuli.Ellipse class - new stimuli.extra.PolygonDot class - new stimuli.extra.PolygonEllipse class - new stimuli.extra.PolygonRectangle class - new method: stimuli.Shape.add_vertices to add multiple vertices at once - an additional suffix for the main data and event files can be set when creating an Experiment - Unfilled Shapes by setting a line_width, when creating a shape - Shape: new property line_width Fixed: - stimuli.Shape: several fixes, related to surface creation and xy point calculation - Logging of unicode text in TextLine stimulus - stimuli.TextLine and stimuli.TextBox can now also receive a font file as text_font argument - stimuli.TextLine.copy() - Bug fixes in self tester of stimuli - Fixed segmentation fault when repeatedly initalizing and ending an experiment - Fixed surface size shapes - Fixed incorrect line_width plotting for scaled shapes - Copying preloaded stimuli in OpenGL mode - Bug fixed in io.InputFile.get_line() Changed: - io.DataFile: variable "Subject" is now called "subject_id" - io.Screen.update() should be even more accurate now (about 0.5 milliseconds) - misc.data_preprocessing: argument and property "experiment_name" in all objects is now called "file_name" - misc.data_preprocessing.Aggregator can handle files with different suffixes: see __inti__ and reset - stimuli.Dot: is_center_inside, is_overlapping and is_inside are deprecated now - stimuli.Dot is deprecated now, use stimuli.Circle instead - stimuli.Shape: is_point_inside and is_shape_overlapping are deprecated now - stimuli.Shape.native_scaling does now optionally scale also the line_width - stimuli.Frame: property line_width is now renmes to frame_line_width. Since line_width is a property the underlying shape and always 0 (filled shapes) - stimuli.Frame is deprecated now, use stimuli.Rectangle with line_width > 0 - stimuli.Rectangle was rewritten and is not inherited from Shape anymore; the old Rectangle stimulus is now knwon as stimuli.extras.PolygonRectangle Version 0.5.2 (13 Jun 2012) --------------------------- New Features: - data_preprocessing.print_n_trials(variables) - data_preporcessing.get_variable_data: get data as numpy arrays. - data_preporcessing.add_variable: add data from numpy. - read trials from csv file into a block design.block.add_trials_from_csv_file - block.read_design (counterpart to save_design) - the main event_file logs now also the standard output and Python errors - statistic functions are now robust against type violations (like nan_mean) - design will be automatically saved to event file when experiment starts - functions to check if IDLE of IPython are running (in control) - several further new minor features Fixed: - Serial Port test can now be quit without quitting the test suite - FixCross, width vertical line - Serial Port will be closed when script crashes in IDLE - Fix for stimuli.extras.VisualMask - Fix for io.TextInput - Fixes and adjustments for default logging - API browser now works on OS X - API browser fonts on Windows - several bug fixes in data_preporcessing Version 0.5.1 (07 Mar 2012) --------------------------- Fixed: - Bug in Serial Port test when no input is received - Bug for get_verrsion() under Python 2.6 under OS X Changed: - Text colour for API HTML reference Version 0.5.0 (06 Mar 2012) --------------------------- New Features: - new io class: TextMenu - new function: expyriment.get_system_info() - new function in control: get_defaults() - new method in ButtonBox: check() - new methods in io.Mouse: wait_event, wait_motion - new misc modules: geometry and statistics - new Cedrus Response Devices support in io.extras - new test suite: - new method in control: run_testsuite() - the testsuite can write a protocol with all results and information about the system - folder for settings and extra plugins: $HOME/.expyriment/ or $HOME/~expyriment/ - if the file post_import.py exist in this folder it will be executed automatically after the import of expyriment - extra plugins can be now also included in the folder $HOME/.expyriment// (or ~expyriment) - command line interface: - see "python -m expyriment.cli -h" for help - better on- and offline documentation: - new function: expyriment.show_documentation() - new Api Reference Tool (API browse and search GUI) - new function in misc.data_preprocessing: write_concatenated_data - ButtonBox and TriggerInput work now optional with bitwise comparisons - SerialPort and ParallelPort have a get_available_ports() method - wait callback functions can now also be registered via the experiment - some new constants Changed: - ButtonBox has been replaced by StreamingButtonBox and EventButtonBox - the experiment is now THE central structure in the framework. Importantly, start does not require an experiment anymore and starts instead the currently initialized experiment. - textinput.filter is rename to textinput.ascii_filter - stimuli.TextBox: Size is now a mandatory parameter - ending expyriment will now only optionally call sys.exti() - stimuli.Audio.is_playing() and Audio.wait_end() are replaced by control.get_audiosystem_is_playing() and control.wait_end_audiosystem() - stimuli.Audio.play() now returns a pygame.mixer.Channel object - control.run_in_develop_mode() is renamed to control.set_develop_mode() - no config files will be supported anymore Fixed: - the Windows installer will now remove all files from an old installation before installing - IDLE will not freeze anymore, when a script crashes - several attributes/properties were did not appear in the API reference - major bug in keyboard.check() - (possibly) fixed is_playing() method in Audio - ordering of serial ports in SerialPort.get_available_ports() - visual problems when graphics card is set to do flipping with tripple buffer Version 0.4.0 (22 Nov 2011) --------------------------- New Features: - saving and loading designs, new functions in experiment class (save_design and load_design) - new module: expyriment.misc.data_preprocessing with functions for data handling and a new tool to preprocess and aggregate expyriment data (class Aggregator). Note: Preliminary version. Use with caution. - new extra stimulus class: visual mask (depends on PIL) - new extra io class: Webcam (depends in PIL and OpenCV) - new extra io class: MidiIn - new extra io class: MidiOut - the repository and the expyriment source code zip file contain examples - 'setup.py install' removes old installation - new function: block.save_trials Changed: - Extra modules are now hidden - skipped function experiment.save_trial_list (please use the new experiment.save_design instead) - rename property block.all_factors_as_text --> block.factors_as_text - rename property experiment.trials_as_text --> experiment.design_as_text - rename experiment.bws_factor_permutation_randomize --> experiment.bws_factor_randomized - Factor conditions/levels have to be a number (int, float) or a string. No other data types are allowed. Fixed: - Bug in testing function of visual extra stimuli - Bug fix, unbalanced between subject factor permutation for hierarchical designs by subject_ID - Bug fix, playing short tones (duration<1 sec.) Version 0.3.3 (19 Oct 2011) --------------------------- New Features: - stimuli.Video.wait_frame() stops playback and screen updating after the given frame Fixed: - Printing experiments with no block factors defined will work now Version 0.3.2 (12 Oct 2011) --------------------------- New Features: - stimuli.Audio: wait_end(), is_playing Changed: - stimuli.Video: present() will now block until the first frame is presented. - stimuli.Video: play() will not render anything anymore - stimuli.Tone and stimuli.extras.NoiseTone: duration is now set in ms - design.Block.get_a_random_trial() is now called get_random_trial() Fixed: - Visual stimuli: picture() method works now - Visual stimuli: copy() method was erroneous - design.Block.get_summary(): Ordering of trial factors - design.Block and design.Trial: Factor values can now be dictionaries as well - stimuli.Tone and stimuli.extras.NoiseTone: Works correctly on Windows now - design.Block.get_random_trial(): Could crash in some occasions - Fix underscore at the end of filenames Version 0.3.1 (8 Sep 2011) --------------------------- Changed: - SerialPort: byte_buffer is now input_history - ParallelPort: byte_buffer removed (just did not make any sense) - ButtonBox: buffer and has_buffer attributes are gone - Buttons of Mouse and GamePad now start from 0 - register_wait_function renamed to register_wait_callback_function Fixed: - Critical bug on Windows about parsing of expyriment installation folder - Critical bug in Block.copy() which would destroy Pygame surface - Mouse.check_button_pressed(): Mismatch in button numbering - Dot: Fixed calculation for setting polar coordinates - ParallelPort: Sending data - MarkerOutput: Duration computation Version 0.3.0 (31 Aug 2011) --------------------------- New Features: - expyriment.control.register_wait_function(): A function registered here will be called in each wait method in the framework at least once - expyriment.control.run_in_develop_mode(): Automatically sets some defaults (window_mode=True, open_gl=False, fast_quit=True) - SerialPort: read_line() will wait for and return full lines - SerialPort: os_buffer_size attribute will affect the warning behaviour of the byte buffer - ByteBuffer: add_events() can be used to add multiple events at once Changed: - defaults, constants as well as initialize(), start(), pause() and end() will no longer be available via expyriment but only via expyriment.control - SerialPort: Updating the byte_buffer is now faster and warnings are more precise Fixed: - GamePad.wait_press(): Can now also check for first button (button 0) Version 0.2.1 (19 Aug 2011) --------------------------- New Features: - MarkerOutput can now send with a specified duration (needed for EEG systems connected via parallel port) - Advanced trial shuffling Fixed: - Critical bug in Trial.copy() which leads to broken surfaces - Blocking mode for serial port - Unicode in TextBox? and TextScreen? - wait_press() in GamePad does now check for more than one button Version 0.2.0 (26 May 2011) --------------------------- New Features: - Overall structure has changed quite a bit. There are now only 5 submodules (control, design, io, stimuli, misc). Things like initialize() and start() are now in control. Constants are now in misc. Each module has its own defaults now. Changed: - Adding blocks and trexpyrimentials will always add a copy. There is no option for adding a reference anymore. - Block and Trial IDs are now relative to where they are added. For instance, two blocks can contain 10 unique trials each, but for both blocks the trial IDs will go from 0 to 9. - Adding stimuli will always add a reference! - Stimuli have still an absolute unique ID Fixed: - A variety of small bugs have been fixed Version 0.1.4 (22 May 2011) --------------------------- Fixed: - Getting a picture from a stimulus was broken Version 0.1.3 (12 May 2011) --------------------------- Fixed: - Tempfiles of surfaces are now closed after creation (critical on Windows!) Version 0.1.2 (11 May 2011) --------------------------- Changed: - expyriment version now printed on import, not on Experiment creation anymore Fixed: - Setup script will not try to check mercurial information by default anymore Version 0.1.1 (11 May 2011) --------------------------- New Features: - Throws a usefull exception on old or integrated Intel graphics cards that do not support OpenGL properly Fixed: - Experiment.permute_blocks() will not destroy the surfaces of the stimuli anymore Version 0.1.0 (10 May 2011) --------------------------- First public release python-expyriment-0.7.0+git34-g55a4e7e/setup.py0000775000175000017500000001353012314514263020517 0ustar oliveroliver#!/usr/bin/env python """ Setup file for Expyriment """ __author__ = 'Florian Krause , \ Oliver Lindemann ' import stat from subprocess import Popen, PIPE from distutils.core import setup from distutils.command.build_py import build_py from distutils.command.install import install from distutils.command.bdist_wininst import bdist_wininst from os import remove, close, chmod, path from shutil import move, rmtree from tempfile import mkstemp from glob import glob # Settings packages = ['expyriment', 'expyriment.control', 'expyriment.io', 'expyriment.io.extras', 'expyriment.misc', 'expyriment.misc.extras', 'expyriment.stimuli', 'expyriment.stimuli.extras', 'expyriment.design', 'expyriment.design.extras'] package_data = {'expyriment': ['expyriment_logo.png', '_fonts/*.*']} # Clear old installation when installing class Install(install): """Specialized installer.""" def run(self): # Clear old installation olddir = path.abspath(self.install_lib + path.sep + "expyriment") oldegginfo = glob(path.abspath(self.install_lib) + path.sep + "expyriment*.egg-info") for egginfo in oldegginfo: remove(egginfo) if path.isdir(olddir): rmtree(olddir) install.run(self) # Clear old installation when installing (for bdist_wininst) class Wininst(bdist_wininst): """Specialized installer.""" def run(self): fh, abs_path = mkstemp(".py") new_file = open(abs_path, 'w') # Clear old installation new_file.write(""" from distutils import sysconfig import os, shutil old_installation = os.path.join(sysconfig.get_python_lib(), 'expyriment') if os.path.isdir(old_installation): shutil.rmtree(old_installation) """) new_file.close() close(fh) self.pre_install_script = abs_path bdist_wininst.run(self) # Manipulate the header of all files (only for building/installing from # repository) class Build(build_py): """Specialized Python source builder.""" def byte_compile(self, files): for f in files: if f.endswith('.py'): # Create temp file fh, abs_path = mkstemp() new_file = open(abs_path, 'w') old_file = open(f, 'rU') for line in old_file: if line[0:11] == '__version__': new_file.write("__version__ = '" + version_nr + "'" + '\n') elif line[0:12] == '__revision__': new_file.write("__revision__ = '" + revision_nr + "'" + '\n') elif line[0:8] == '__date__': new_file.write("__date__ = '" + date + "'" + '\n') else: new_file.write(line) # Close temp file new_file.close() close(fh) old_file.close() # Remove original file remove(f) # Move new file move(abs_path, f) chmod(f, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) build_py.byte_compile(self, files) def get_version(): version_nr = "{0}" with open('CHANGES.md') as f: for line in f: if line[0:8].lower() == "upcoming": version_nr += "+" if line[0:7] == "Version": length = line[8:].find(" ") version_nr = version_nr.format(line[8:8+length]) break return version_nr def get_revision(): proc = Popen(['git', 'log', '--format=%H', '-1'], \ stdout=PIPE, stderr=PIPE) return proc.stdout.read().strip()[:7] def get_date(): proc = Popen(['git', 'log', '--format=%cd', '-1'], stdout=PIPE, stderr=PIPE) return proc.stdout.readline().strip() if __name__=="__main__": version_nr = get_version() # Check if we are building/installing from the repository try: proc = Popen(['git', 'rev-list', '--max-parents=0', 'HEAD'], stdout=PIPE, stderr=PIPE) initial_revision = proc.stdout.readline() if not 'e21fa0b4c78d832f40cf1be1d725bebb2d1d8f10' in initial_revision: raise Exception revision_nr = get_revision() date = get_date() # Build x = setup(name='expyriment', version=version_nr, description='A Python library for cognitive and neuroscientific experiments', author='Florian Krause, Oliver Lindemann', author_email='florian@expyriment.org, oliver@expyriment.org', license='GNU GPLv3', url='http://www.expyriment.org', packages=packages, package_dir={'expyriment': 'expyriment'}, package_data=package_data, cmdclass={'build_py': Build, 'install': Install, 'bdist_wininst': Wininst} ) print "" print "Expyriment Version:", version_nr, "(from repository)" # If not, we are building/installing from a released download except: # Build setup(name='expyriment', version=version_nr, description='A Python library for cognitive and neuroscientific experiments', author='Florian Krause, Oliver Lindemann', author_email='florian@expyriment.org, oliver@expyriment.org', license='GNU GPLv3', url='http://www.expyriment.org', packages=packages, package_dir={'expyriment': 'expyriment'}, package_data=package_data, cmdclass={'install': Install, 'bdist_wininst': Wininst} ) print "" print "Expyriment Version:", version_nr python-expyriment-0.7.0+git34-g55a4e7e/README.md0000664000175000017500000000420212314530407020253 0ustar oliveroliver``` ______ _ __ / ____/_ __ ____ __ __ _____ (_)____ ___ ___ ____ / /_ / __/ | |/_// __ \ / / / // ___// // __ `__ \ / _ \ / __ \ / __/ / /___ _> < / /_/ // /_/ // / / // / / / / // __// / / // /_ /_____//_/|_|/ .___/ \__, //_/ /_//_/ /_/ /_/ \___//_/ /_/ \__/ /_/ /____/ ``` Overview ======== **Expyriment** is an open-source and platform-independent lightweight Python library for designing and conducting timing-critical behavioral and neuroimaging experiments. The major goal is to provide a well-structured Python library for script-based experiment development, with a high priority being the readability of the resulting program code. **Expyriment** has been tested extensively under Linux and Windows and is an all-in-one solution, as it handles stimulus presentation, the recording of input/output events, communication with other devices, and the collection and preprocessing of data. Furthermore, it offers a hierarchical design structure, which allows for an intuitive transition from the experimental design to a running program. It is therefore also suited for students, as well as for experimental psychologists and neuroscientists with little programming experience. - Project Homepage: http://www.expyriment.org *GNU General Public License v3* Florian Krause (florian@expyriment.org) & Oliver Lindemann (oliver@expyriment.org) Documentation ============= Documentation can be found in the directory "documentation". For online documentation see: http://docs.expyriment.org Examples ======== Examples can be found in the directory "examples". For online examples see: http://docs.expyriment.org/Examples.html Installation ============ Detailed installation instructions can be found in the documentation. For online installation instructions see: http://docs.expyriment.org/Installation.html Reference ========= Krause, F. & Lindemann, O. (2013). Expyriment: A Python library for cognitive and neuroscientific experiments. *Behavior Research Methods*. http://dx.doi.org/10.3758/s13428-013-0390-6 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/0000775000175000017500000000000012314561322021202 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/expyriment_logo.png0000664000175000017500000012034012304063646025141 0ustar oliveroliverPNG  IHDRXsRGB pHYs  tIME  b IDATxwxUEǿsj(XXW]vm`cUtEQײVV{WH!$sބ Ƚo>s03s)owfD"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D &E H|3 06yD Az)ID" ,D"O?aqq98@$ 1"vBd,f_ MSE'_tWRH$A"H$T%ž, *d܌]`I#K"HK"מŹD0VjT 0j9x҄q~%Hd_X(V:FLڼe_n;SM%H=M˂cH㎜Z /T9oJ$^yL俳kuM]+*ӱ尯pKΊY\<JF$eJh4ݎ-ܘ`JJ${3dbE*zH?[H$He p:mv(JA'^vD#XrsD迏10𤧓 1jt@.\iC&R}idIK"!!Af\ؚ_&K,Yv.ҠDψ#e`vnW(CDBsCp*3f`u%Hp!eeBaJ˃`Y ƘQQUyrDy煙 +#=y Xf-9t/].mC$ ,DG+De2tJ$2L᠏ūbp1 do:K\<0ګN8 Hf/uP_!i`I$?>CT;E(u[1ư@ѤY!+=zzRZN~ c |mYhޕ``S Hp!4?_l9!1UVA5q\"Ho<ϫ,˺[LQ~3V?3!jUϝ!i`I$?^|7?yhiAQUŲ`khZ,. ,d3^5M둞Ņ(,Ume!B`uk7T~t\"H$H"qG.Mv'=i @DRTe#7B4O|K$xFpΟ$a}kp sA(߸vm ѫ4Z[C\~e'6w~1y^]NXd.Z1L!vZHq=F=R:Q%;v?L'IBc23Ӕ!L"D A"eSc#Lc,f`m}5/a 9Y/`Q҅@yHlDLeqK]aX AUQ|`h!"chb` \:o;w^}Dt)QoDΔkص>XqePD5 Ӳ^"nƜ(Dp!d wEcUD)Lo3{Ac=8ʱ 2k%S62qGB$!ͪo\xb(vo߿!8@gHo]#@MQ Ԕk_/{u!D4"vjr2 릿*9wB*GDde(9_( D8Dr.{n3[mjY\B;[s!kX_nX%X}nJBPFy|yж/9B !~Ag @h7hHf3{xSvVvB`ſxeo(9gZ/l0!!<`pi0E3X . \q?v̙=!he<< C*TU [^knYv͙=}$"Dž2v&SOaʯBoص7gøs"4*)pg3!DR` uu,^r]rB)nK4iEQV74_{ݝ3{_nO$~i֝v!a+Q ! (:33Sk` H@B{@0 cMSb ( B97x]"gd:Ms, \D[mW'>\00,oI}r=ϺfbL9LDk-5y}Y/r_~쮮_{iY9Lj{lN ?$L,J^k&xi֝N r33`&,!eĿZȴLkZt >Pk[]]Χ3, i#m#[LU-};k~ނݘ,Ҭ;Gz2ҳMiֿ!3;˗/CCC7Nˏun{<'+"goE̘mצ0eE3蹇q330,~4OY̦c pΣܲ` ÄiY[ Ω)O0KA`YwRe՛ ΣWwoug 0#y7ϘUQٿ4QBE)Tԡ.(A ]pm3AI:wv- X^}Żxi֝!.d'"[۪ i8v0b "p!b_ND0L-~"B- C:ch4뮏 Mջ ]5+cs1gL`=9T&UUEvfu4݁־+dž@\ HPiw>1yG`o~5<R"96ׯ9Ֆk?>>=г[r2r:Mhj<6ߝ#nv!-Ń|gCf偢j4MA4(,jҲ8+ 2"a@8" -- )*CaD V !}ŭB"|_Ᏸt:p9t M|o2>B4w؉ؚ`A.lT^A$w?-V0_Y{%` G:~ֹ'ܾܲm?ъ no꼴%wƠ3-R2`s8zMxPj*7͛A&ޗW|s3>oN;{ (|F,^=-_='<ceaHqfgt]9fwzu-ċqeοMki/u3]זAC+%$K ͘8嬋н;v;"j;R$$j*oS56My9oĵhg<_%=wѣ'Lӊ:rijŪ% P__Eak7T賯m~nߟcvC>9Sjvp!@(M, CUO1i~oͳѓ cfG LlQe܁IA?~|M[hu,CeʂP8r v'\6-͓̼|fT;TU˓FϜTU vQyD !`HH   3@߂-E"TJ_nyමM -s==?Δ$oذf~5y qRsR3YTUYpٚq/Xy[U"e`Y?qHP "4mM(_x4Yn' 醓N=3֬ZWޖ˟ޫ|w>X{f<OR2O>|7-v&&"[)D'ykZk/ vXQ5,luO9#< ];U O=rTVm}Нޕ̿;EaKg^pq/cy۟G-^vu,zc=Э6"zT8KC${NaC`1fwDM$ړ`&eb˪c +3#~[xw3f<0Lo|Do;ُ ¼y +71I EaOo>g6][)D}8)OE *ݏ窦f_I<ЭYvXגAIέCD`$b/?ͦP823zeG>t9DxnӨ~>Ɯe٣0 6Z?}O`iڴv?g6J?1`x:dl}Xp w074sG=9J|+W%) D|bz?Ueeg#-=w*l7as8c3ֶS^ò~v{h9S-8ᙇnU~K (ŘcNcwZ6/](oqv}Jl 9dι:֭g_6J)߷Wt)/^o@(|^]ٚ+ DꕓvO+he`Z{rrڔZ"aUE}7 o짅WK{WH\QT5k=15+32 zbK]|y >P6ly[yf˽ 3쿟L/)ACS3r@NL]x=5xc#aF IU/F}S3`:wy)ؔk6k맄p[cϋHTbM-9&ݞ  m]׻G==6exSz55@?ysOH$7 {4KʧP8rCnN<ީ-1NƟ  ^{""k[9vc]Ҷ̗lيL8 '9!?>/;~ zG> V2bK.Dk{G( Íaqױ&oߙ:l[|eܩ@paK}cz?[ث?u/ľk>[)‘ȕ{6˜EL:`)=|< nΘa0xQ؄-lFIXFe᝷B ĺ±V7Ӟp:\.)yw- 2pwFr8 >3Ϙ|ąQsosK#suQUӴнdffQ9@N^7ZZs8KMӄ;-cd硈eqJگ MjiZu1Uږ'BQU\|mAHݑ%83]:aٲvbe }JK R*oV5MmMSs-vALSfP}5ل LKnKN`xݷa52 jL{-Sp&o Ng,PPKzI`os:0;6N_Xݮc術`;:.'EAC}=~TūP%hMPTܧsrᤓO-mfY:D"z`(<#$WZu;w Jv%=>%0``{7/}a-@)CƘdIMߣe #FEA^ !cX]4ssQfww N<Ck: ݽ8w+ɺ0Y zڙ I}7:y[5 0@)i{tNF>MM CWUz?N(, >pP-T}Wj1UTc;PXLSxKa|(/G$} _r uLה?-^2FDo=))`_iiiS'cⳏ4t+kퟕ%S 03<(۸.*}KaqΜې*G9w@ayEsV.Rbؖ5q%ڬ !d!ҳ;-ߜ.EޣniիV0L%|)]t]nOe h ci bj!-#S߳n .^{~S \PܛKse*j,/Gnv5e%>|6*>tJٯu*p8]0dFѱNDݸՃ1TO=#䥨 Rӡv ]lnq~`Ĉ.W;1UUCsKiHs/UU0|1-Ld!dgߑw?- a 3@ uI}#GCUR=dm]gXCe D]SEMki4"Ѐv۴S2mD#A))8ܩ{>*J{ N7kapUtΌ^FFz 8 ~ IDATay+O?U@kW+S}; [.Ҹ%r;-[Mw[;}TZ|n}D^^^}P]kvMS Ssl6{{1ns^Hc\ 8p,c{-ڐۥAp_Z;{Ne.Elrtih񇐖(.?v%8McÑ&Nګ#Up8Y X{,n qRi 45Z~y\%bY}ֵ] A !< %+S;]vj*((J8a0xHZ޹zo?bt뚋c&2]%6zƈUV?r]Lhios~hv8_4xaYn{)l4תukF '~]ZQS[ r3lqɕUwa]< d567 ".'&tGgu>27 6UٙiiA`ѩPB(j=klt"Xt:l83`OwgBbe ,. 4}^vꄢgLS/e1rhTN=Hv`s'SYYs<lӖY^ߢ"n~)OsA}̜۹* ʫ'سw_Caxή`qyƀ1GNU@(lHO)αXt$t}0 )9Gà0u.21@ CDpޱC.E3meBXiKKXu[~*%^;fY7eY:Tc+2^}T }La4 8 t˭ݝKJaJDiE%T.,K{0v}Aff&mjA[`/,CnkW>^i`3||^ߵ[I>cXFn45hJ@15f4Eu)fKʧj|ֿ82aTmqޑ6WfgTW_ Jl1r:w RHsSbB&w~y63meٯO(-!w,ChC߂Ͻ)Tc*Qt+e}B\.WRPT Vpv.T]_tmW4vQ 00~J6oѼvY UU M./ۈz}blC}]-Fggtpݱ׮kij8! !]L)` ò+BSU<)m={Q7neY¡֟弉YPɀa,Yٶg˖.ImאqZ] Vlz*E= }9tS64lWzşt;%3vSF[`ӦJ|7Xx1|~?2220ꠑ8CPTTҖ`[eWC c.Iuh횦!;'+Ss}͈D"|9 |-6nZ0[),yKg`7\[_X^i:M< D") zV-_r҄%zeuݼ@(RaVgiJ۳`.㖫Q6JԤܻ 6O?ԡ=/=g3䟴ySz)IG]fM8ͦGcB ^88G/0^bʰ5=N(H !xRwv~ƒ%? pݻ$gvIKπŅP[ҴynwEַ_ݧ/[l]㢜Sv]/yqp .r69lXrؚnoa ;fPԣoR}i[cpԑns9$vfӡ hbwN#e+kai_8ܑX?0"n!EaHlo;q[PMS_ЭSiBn&|A6]c ,0P,#+3vun ތg{[2|)HKBIi?uyШR烁Q0 dMqȑ'D/MBDo9)'k6T} Q. æfg?zҿ,r2xpfRmrnݣ,t[Y4lIuv>bt8:4MC]/=7B(6;(,,SWWnPMzSa_~.0|#zi|rZ 'L:vd؝)(-իVY+UnYטE8m/;D@3Z#U1w<|ؼBp86z\puX\@#GBw~'8hn1lv8猈[Ucv5JK݇>q;pre' \\ХG1xQm*3%M "}TY8$3&[˶,#G'#gNms]+UΏyatR\](Vo G:ng"}@a jȲaw6.wi]ZyЭ7SAŦ4(El:K5-3ͣf,I# KDB_鰁[.9ԭ{uc |o"S=Ƕ{ƌV]Zv鰁=?үLJK]:֣GSף&*1l]Э7u1kد7U/mhq:lj9iRS\2f>ʒ,**X|mŜIGkj& ܬsUU/p'CB[媪VT͘b2uAa+S\?s5,:զk>G.*O[zu,[mZU"º}jS>rv4'ɧXg'  sA"~c#gޝ{͔7†+]yS~1G``Ou`#eÜNr'<ޛ/㲅QaZ²MuKbpĆ-wܨuɜofee\UPBl]}Sㆵ3~NgRCvn|ކ.cF5N')kh9NpR2-k u5kVC !5IpQ0#S(L4x}ؚ%vNICfffRE\QUY4M-ojSvCI|=X]cߦa!8H>5ITuӽW"%,XQc*&d%K -GKk2̨mVж}:{)tΌ5u[*l3@c6ΨlO1EAMOjeK @pn=CMQ&ev%=Fc*1֦:f=mX/s_)<|Ǣ/=1`iX8I-aYj^jV&:t {&‘֯]ŜN[CSs`v 먄BQXߺlْ֨w;4s>A0n=KXg"hBRUXNK:9z,%MSYrwk E*rDtiٻ_KYY )#{$Aa`Y5PQXfZPxe+$`յMż2v K$VWqg"ug^zS}מW) -ݬ].:J #K0hȤ'TunIKłK V| @N3ɟm5deqqocy]^M͍y.--2Ģmil6w TU|c/c6]kZ_^!e n L̇%l- *,Emr8=N(Pcs`-4ޡ+8k^ܫ3sҖ|HEe.1/4EauТqסp:I/VVTE%=݌aPqp8I׳<Ujj/Ɯ vs.<&Bk~W"MM3.>}Br#TPani_,k,:56(`؂6f9I綍b#6ժaP3C>fq6R&9, IDAT8p"  L9/19o_0bĨ~ n$b3O'MC/y[?ǖZ/MH Ώ-EGy .[z'9QԲU(BKL G"7T։CG| вdECVNkhKs`Ȱc<,x" >bvKHB:4 7_BlcrԱ}z ϔD! Ĉ4yn7p~jvN>l6=+-031kЁ܅[>;e`U0L蚺a;|W263Ysx^޷vI.S1#'v:tÆ.+U1kҴR ]+3eyp×YRJaޝswHU~Zu43~0$?3z0ֲ> 9_|RF4f!BR; K#;'ʦeҋi4'=7ge9v Av t'{=^JwL}=0 R}/iǭ};Ųe+2/X u7! i;7W[f,L5?bOضȊČq'mKi"a)Gq$o!n'_޵c _|}?pz9r8CG1Vwe>u<6,SO~n{p+=Td/gflvl҃; ,Վ[#-0ْ2>-MʛZ _җ88Cޟ#H>|9(JvK3je! uB()iapphqrq$s;WďD"p躭y_:|;@wCs{w "<'%B,ǿ?v:4[qq> { ~C)FGk6ɹXf)i3擝oNzs{IM8kB"Ќ9KX %DؿK)(J>f.:ckfgGHxmB&y}愲 EcF5gC]Hi3fVUi'f nDΧ ܠًJet)6o~DNK2XxspڃO /s&z1;(=iH$ppxfbÃb]B# F/WsY@4f6Tiy\haUK3c_ױi҇jۿsIiw@5K I3z:Ij"ϫY1wVEv\ޭ-- "1V_$觱#-Mfk AY sEMKW0P*}(|c0Yz)% J?|jRjjytv:4a4+IN8/ }`L/#"N p#5a,M>p5 R x4M/xbz!v;3ta¡kV{T; b#Nۆ( H3L0d&瘯m;800>v>]@B|b fh cx׊2)eWP,K/>O&c4][%WC f ј:2>R1/l%mm Cq$ݙP(dЯ4..o?gvц枿XAɂZ]wR~Am!q Bprf̶y=TTϰm :],|mQ]]cKу{K5MLC]N]}C)̌GbW:7ui$'XS^(+clNdzx\. l-m])yѭo6rMB{k4)!+&s m߷GG[3KIS5~-KKI L±뿽 e"n9Y\N*f⿊.ӟe/h}wVtdt¡ ::Y4H1| XzD!c(BB&so~PgO`\XDpc?'.WFȮWBF>z H ƈ`fOW-T^Qa{|]x)Bw_DcF-%.= .h4Mt2[^E U\<[nt:\oqsߞ$0bq|/%K!N8m+w7Ё=QUX+[53S^؁ǯөw +J@YyuF(,`1 YLj΄Ajlղy f ޳:G)URX\̺n/*RnH7-ZHhWo5$¿U3JSC~aIF;CRC3^|ߝ˽DbҀ݂ ΌYTTTZI+g~A%ζCE$ 岇Mmle0-0HB>"4Kl+K۷JP GZ./i[i`=s&.;sMu5ٹ99x< kR.BVbxͿoS/IJ#rR 6{޲jrC)`>fj:&߷Dό:G)f[L{zIڙG/%D͌yP{6h ϤnJ>{! ~M R Iɔôlm%} |zGȗco999ޞ>m8e3SVP&yǎ&)u1E)+(q14iDch1W㧿߹^һy,[ 7mt^O ҸuFru DhpiVcH!𪕰.!G'ե/1NٽkIe n q[Et8m&8]_BTuH'D'ŽeeB80H:!h93P^Y ˲ l}-phRcQfe嶭 6߱5v:ǝq̜ ¢GT8chۥ!/v=!AJ:89& GYפF{ɮ b0mRr/] Jh4Z([:v]˫f&2#aŴ忸 @ XRR^CRg–i[lw=_B[nw0 M``tB9jLD:Ӄ[kyu7`(-Ƃ%R03b8>Op"8Gv@@y4%sIkH1'$ͽf)EU2H1`45!)?nbz13fYLv[z# P]r:N;wU4)_%ȵ]rAdfBפěK 5MJwAa! ] HgϜ"34Nt5]bz`˖W$E#ExcV&#Ac{? OԷ *-yQL&C\jJ@_2Ʀ&iF0~4hj%潤e+_J)Wp+IrTxDqA`bx]*δG@@ܰ@RSHT}:WVϲEJHz#H rȡ}(.x;obxN95laK9"" ǐ ;FtmnAiFǦW_ Mhʹ=]@"|zHy P\Zn{oL™4DT}u5Mjf.M>X@8gy*/G[xC ACvtAꆩ@(ޗpR+;.^1\.GFygatMyw"ۤ ̞; `S֤3FhRR%W NظEk@U_ Tx\v8dGuu R'nQ)"̪e0@$j=piShE"%-'>x|oMM5_^RJATq{? n"Uڔ./$d,h &Ahi߅(iKRXq;!%Mܟ@&%P8z:?ooitIR k`鈽{;1`w_xuMR@'|:4% Z%\e!4!Cqi:B\D(eL+}+ 4e ɲ\^$aw˫ fD;0br? AKߐD#$"RދBʇU\;}-[^ wd]r:ϵm`uvv@"ŪI)I"%t[YJ!3Ǜ7EFg.cC'>xCbBAHIj/!!c0Ͳ͍ 8tDQ.lg]CKGK g` T"n-cc]if>DR}.|7;:J)l`}7>zGLLx %KIpڽAݰ}̘;ꄰ8~ !A`tfܼTOev R}?n@-==(,*HhƦfԟ-!@s{aK7LTN;rx~03{Lރ%h] bu+K Q\4L7ނ4c:oNNW",Kkr|9op*ųr ? [wF)ƙuĞ_ ,]m[Ja׎Т܁DIz[``<씭O}K_d?nd۞nD"P"!hi>gU  @8Ht3}-؇[m߈[>!W[06#}#!6XzǏBzb'0&bR~&gJlB؂ C, o\0thwjDEp+4Ra UTM'7}YfvWQSRťLBچh8aoӡ͔F-`fٵNf晤.hu..xSM\I]MD&pb/D S,ť̶#ǎ¡K FhZX*gfg`mmFp:dBjW D`ah\Y}\|vy=((,YKQMC pz~U\?t6„~R%UKIaǔdzn0ftamkQVJZ9ׯvipcGB–8؞ SgBd'UbHG1kY `tw$b,0yFC2L F$17U#XFu*.|?RxuӋi2ZS51)U}775"7;@a! hB\WBwW!/ vko.7YQRVti*vF.3**(,%@:躆%fT2UVQcAq>3*K+X$<9yi:H۬x k )<_ճe]z5߂(-yةMHimC8|tiy:>-P|,.,ԟ-+0mƼ /<$OdQ9Ep9uۨgnjMJ{#@[”jM'"E BqImB,K C ȺH!nyw`'UIȴd E`}H nQJaڜEdG`?09Q9ޡf.efرD@s[6.KR 8sԂ_'PXmԤr޾8Y* )&B?z/3ow׬Nƾ񓧿KgA;ww Nkw}7J+L'ER ;/5M -ͧI0  > :]:yGmRH+^k首W>$󸞪?:}9G=ҟu;5;G5BwqymWRܒtG1W$n7,"[ICjyX$XAIymKR +?-m+N˿R^P8Od0[%K ɘ~Z?PU=׃|I!.2Kt. ,%%CIi4M}yqVNq(KaW04P0ƆmI]4mmdʂz)4@(g$~z0c*!L ,[3Z[ cG]:c3IikeBРR<=z܎uM`Ɯřet=;tcÜq)VeegAfm[_3LSf [本\AnY]`85H("eP]=e7/.{ż^oDwrGIavb |?/Yy3J+Ҟphhl*q.4tͼ z!ʂH|h('xczۼnǺx{AwWJJKv/]_ۺٹt^ǚFkۓ5:M^1%4m?fϞ VJ_龪 ~k'Z^LXi%on:d~[v6 Gy=vyG'tl׳@Q{<,hQu]LD9:R$0NK=n i08JX̵#-L*4ḦFD$CX7R ~ ei·}fh DbFO{ )Re `0p$‚FFcL)E i7`|a;o^ C%vqvBWO+WDX6(rσ%+Y̙&deܞ|MBw1*(  XJjX̯|T䀭7 {viܳoTV":9D(pH=P(fifWα,=TuH䬾ox Jكxͯr`(xOyǥ3 4H$'Kfc0m7LC0uI;~V)~|ZM%/Z+ũ=n'F,DyΟhR!'˳(*.L[/^^2rs<7x%yYϛ{d)DVOa޽PJa09FQA MiU~[(ey] jiZeI{-tz ,& JAGQaK+HS{hA0#F$ˈ\T0i8<ِl@RG,nP ԟ8 3M92?mךFQ^6/YN,tw\.!!xɩk_9s/_6R6.R'_`8GLI !PQ9 DS ;vlRY/} K98.Ƕ0݂ZSͫn ^)Ag<~"i`yX񦎿}lb$1$_3>::7 DR)+CիΆ招:zo)'˃Ӎ]_KWmIF`3}M GʕZ bƚ+/cϞi&9~~Ⲣ)SY4b&u7qq%gaF0_{\tSt\r;g~7w| g5aηءKu^؄P~6aY c]wЛ|K}{wY 7- *ε̈E8q=ϛCnp: `wmXRHT\EӗGʦqF}|e|b`jlRe+?*q,49MR` >j_xlXˣ hu703[u m̈M%C%ArSz>z*(I(R ݻ N Exs.hq^Q%4)y7^/ns儮)fΚ3&  %-b ,,z:]L| +?J)^Do!RӈHRRB/_yhV@hҸu>y ,]$T(ζJz}HTFšndfOIlx:huavH@FggA')EP6iLJE|[gxt+8\{[ɡk-!>>)Zt"U-\.7}4Mt&e0~`tRs4MLDnAM{ 7'ɲ6 FKY@4j tRUJVL@$fr:Rw|; AZ,f0bPJ\1Dh煮c5%b{>}:  &̩'nXUρ9F _; Uw3̘0~2nR@,>ufVCx ~,@ {peupjg&4M\CDDhl;ēe}C)^`b.vUef vP0{c-<#7|:RaaaZP1cܹ$/ M1VX0_e$\oA8Q +.swF.I-MӰdō {N\N GN|yd'5q<7R)ӒH$H$BBP4f Ņ- TTMz`0 H% JA@p EN28v/V@z(f ;b՗?qòԜlv:Dm!K q`nA &gb/,G&S1mu cH1+RK\.7`,A@(F7*W:.vAi'f`玭, DFyҁ]3N]RggLj⻢8z0‘pohcUXoeq}0@_u-ИO2qhjzrsQ:424]5سN?FnL矐H$%dçp3, oJlCؽse\]IBDxw {܋4PXW RLi&#8xr0DA DӢ/-.M [+H4ލ&yEUcʤvH)-'2L[.O=t0 hGJ/!C m$]^afȑCp[:󆕈O9RjONG[(J Ug_AFmӿ0*+ IDAT ΕxɏT?0l_†?StY~u\5!wҤt &a[;Ԏ~xNةblx[ABhEÕ?PhAܰNm䦜R8^U3 Ũ=F&_vc-u.3!@1{/SZeg1I'"uBYEW_7Ҳj6MVjR`(!HA9.R/DD1BЄկV Mi[A ?{ H81jOϟG<2l1z@İjb{Lɪ)Հ 1fFb=J&#C` *TnANeݣ(˂Rj($@_+:[_K#USWR[XJ$:Ry9^'JH8MuEh$fMYADm3M @>RG-erFZsR~pr D~5M b+E9(.5R`f޽zIyodqx2S*TczaY\E 5BH,+qx xvmBk).WwyqΝR vmX] _@ Joim0,!I7?,{}ήno{H.짶Ne h8`8zb/j"x Ky]yt]Օ{ߨɖdIl<[16S3T(̘JLJ/NWEA$MS$T PIA@0e[$Kekw:xYϲl,ybketgsgr҄@$4#ig}~ %ep]<'L$q=;C& )NhKnj>k ,J*"?֮}RzNGI 3tMRmPa}1 VP")![%9d"uۨ@?,O/V[K#lPYUlǝ2sZLO?H)BQqtyΐG)h,?Xlٵo=UxJaM  W^~ -m/ =e TP2ٷјX¬Ge4I):/*rQO(lٲ =}#(]@)5'bs0t-H9BƇ !nxMs7yJM0P>d' JBw_ǮMlVA؏wSk hjjַGǕ8׮R* $6n$CװG8qݦ$PQyNs}eM#>RGa"3S7ND 6(=U_?a !44~ꖮ魭4:4*Bh9خbܩJ_=p׍ jUyM-9"|9 '>cϴv9%J AfthQ$⢜eyEiϞ@UylY$Tj*$e:nDcdl>}}?A* . C|#ݥW?e³=H7z`ۇ~L]K?Oq.P]](E@2X=Јֻ32_/ʫ4@蠧oY_=t%t k9R {삮Z7{<. AKNd+փp QomUo;ko߁Kq:Az#ke=jA/R"w OH < J"hYԉlJ9$t#u&vk}TF> ;g>Cm MRŕW^\WVe DP+ C_p{djOW2u$߼_pI׵{@)]1mb\ӽ:VDbIخsB @eiъIl?b8R'B~뷔.QSUҒ+y_ZMZ8ĦM.bFtNPI >H}~;͙=EɎIe->=*pQ(ϻ vBmԚ iV5.Uչ{tfk6ze헺;'WGA8gUMM;8KZQjB-L*s_B ¸<$q =s}װ ոWUU*zt4%v}mTZ pgʖ~TO; PܙUB;݃qCc;ZQ{m;Jʪ)5*T}pkjgU6hh8ViwC?yЄ iMzW?n[cΗ]4TŷQʼױRۀɗ`'[c2K}vhTTK7ͳRڼy#fO+vVeӎkMo]z)nT|c:g, :^+!p&rďwTwk,^trq*>3ǽBI A eTSsSV-Wcyf.hn@j97bsK@ayj:7bմIkuۿp9[C㞆څ"U:ݦiᩧU>~* s b <΃ߍ%:诮zu@{gT=,^DǼRP;z]#n,n_&UPꂋ.9g! D=fHܻmn8C}b1.r5s:\7Q–!@WOd ;ߞX+i< ݝ!݄577mȺnԄzcKZr:o钼oY m޻n[Kqޝwݕ {MY{!ʧfΜ2DA8C]C<@cS3n:8Pm G^9b sQc>,Xz1,Y`W;o.@YuZݸkX铞ؒZ\zʼ7}"C'O{ftϯsy$mBx-T]Yv"0m y|њ {o"bguϫ.vrIxe9I ӆe$UPlj>?Tk'w571vוOGUVL ǑGM~%&ڻmwZ2Zܵh_؆H•c@!@!0\q͍(+;p1n:}PJ0@0`@״-J=3w4Z9U8:?|I*ܼKP5cINe/Z_Hl~1{J*W|\u#7U[wP4 qSAo0``ܩRӵ;~ώ&lcO;œ6lOƞgP> Vh:v? o';UNl! G } ~` _c|@w}鏠~G+qe56n;U3PRjtExuxݹ]G~/'LX=z:'bܩ[w^cx;~ ]{WM~ȕDBҴaZ1SDɴtq R5N ot憖0Ό>O c6?#qg 躆OǔB,;uhǞ Z},y➯_30l8I2ͣceZ =5ӦNie~WI3;v)v>2썗J Cl3aIt!#K?`uEI9U|\T#R*VJT8HY+cNi9јٸy5J~ϛ0#z´ Ex-J*RGD4]AӄMv'v{+4Me;K uǕ4Z2#)doX%p[2 ۰? 67<U5M+iڵCOZN#emt7+_}w~!ʱ֐oOo.ƒik폵z2x`ibU R"*QJ\)u/H:;`ZΡxj޽/fgXE[Mk_XYn9x,N$lm>qL뒦  N(Ǔ_xmo1F<s߷M˹8Mb SmWSr$iB C-m}pg{zwAxª&4&-d<#Dt#5uZn3:&yޟ>OvIY;&26qQF]8NԈ6E1>2 y} zrF7'0;|GKˋ.:4-8O9Zn W{2|DLGՂC9JՈᣏzC ]sp %{z@xs$#K3uo c-7}&aoxs]GG݌n7NkX^{Bv'H;^qdY{$nnNsF'ҲԲE93m>!fdCs~g"%3$-#א 8o֌gA>{XZإ<7->N;0L /1OkAlgV> 6M384N5چQ/7F KfG9{zy Y9w@+8noŐeR1 5mRlZRxg(~,Ejׅ,ׁg2ccrX{c~:{mɧ}ug^x,yʓ~f;8xs+y֡3:ހ}TgKU!T0D]'Ok׽asAH)fixJ?NX@Q|dioL x,9Eqԅ 3 C- ǵ ۔JjۊQ)|DCL^Bфu9 RD]& ȖWqd1oZ0̧Ozs˦E)lK)ۖhjn&KX]IDc&,YTXEvOKn3eh%WR8ԕRHR~O<G,RgeOS{,@G{;{0g\Ep2 0l`1i+Mn$Pm ㌰olg7ۃ++TMJT9 "9J.<e0 ð}-7=6U]]Id2 k>]3-4(luI@a`*+``p|rᲞ6W 0g1IyiP&'TVLJf>P$:$8_v\v,VQVy AT9>3gƮ]aⲲI%gǕ|{a,X(s]PY555ZU.@sntvH4yagOdΰأ%V) ŋT2"Dχ׼XFuEծ]W\J)(Q VD47>ǕQ^j2M9,w]ah-lMPTΞȟ ?# ^3 0l`1cdQzF=pFx>!AZsHa|@GVgab>z"2 Rs&ߞ˔\<ўaa9:C0,ptsS40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ZuU6IDAT0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 WpIENDB`python-expyriment-0.7.0+git34-g55a4e7e/expyriment/_get_system_info.py0000644000175000017500000003313012314561273025114 0ustar oliveroliver""" Get System Information. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import sys import os import platform import subprocess import socket try: import OpenGL as _ogl except ImportError: _ogl = None try: import serial as _serial except ImportError: _serial = None try: import parallel as _parallel except ImportError: _parallel = None try: import numpy as _numpy except: _numpy = None try: import PIL.Image as _pil except: _pil = None import pygame import expyriment def _get_registry_value(key, subkey, value): import _winreg key = getattr(_winreg, key) handle = _winreg.OpenKey(key, subkey) (value, type) = _winreg.QueryValueEx(handle, value) return value def get_system_info(as_string=False): """Print system information to standard out and return as a dictionary. Parameters ---------- as_string : boolean, optional Print as string instead of dict (default = False) """ info = {} # Get platform specific info for Linux if sys.platform.startswith("linux"): os_platform = "Linux" os_name = platform.linux_distribution()[0] details = [] if os.environ.has_key("XDG_CURRENT_DESKTOP"): details.append(os.environ["XDG_CURRENT_DESKTOP"]) if os.environ.has_key("DESKTOP_SESSION"): details.append(os.environ["DESKTOP_SESSION"]) if details != []: os_details = ", ".join(details) else: os_details = "" os_version = platform.linux_distribution()[1] try: hardware_cpu_details = "" with open('/proc/cpuinfo') as f: for line in f: if line.startswith("model name"): hardware_cpu_details = line.split(":")[1].strip() except: hardware_cpu_details = "" try: with open('/proc/meminfo') as f: for line in f: if line.startswith("MemTotal"): mem_total = \ int(line.split(":")[1].strip()[:-2].strip()) / 1024 if line.startswith("MemFree"): mem_free = \ int(line.split(":")[1].strip()[:-2].strip()) / 1024 if line.startswith("Buffers"): mem_buffers = \ int(line.split(":")[1].strip()[:-2].strip()) / 1024 if line.startswith("Cached"): mem_cached = \ int(line.split(":")[1].strip()[:-2].strip()) / 1024 hardware_memory_total = str(mem_total) + " MB" hardware_memory_free = str(mem_free + mem_buffers + mem_cached) + \ " MB" except: hardware_memory_total = "" hardware_memory_free = "" try: hardware_audio_card = "" cards = [] p = subprocess.Popen(['lspci'], stdout=subprocess.PIPE) for line in p.stdout: if "Audio" in line: cards.append(line.split(":")[-1].strip()) p.wait() hardware_audio_card = ", ".join(cards) except: try: hardware_audio_card = "" cards = [] with open('/proc/asound/cards') as f: for line in f: if line.startswith(" 0"): cards.append(line.split(":")[1].strip()) hardware_audio_card = ", ".join(cards) except: hardware_audio_card = "" try: current_folder = os.path.split(os.path.realpath(sys.argv[0]))[0] s = os.statvfs(current_folder) disk_total = int((s.f_frsize * s.f_blocks) / 1024 ** 2) disk_free = int((s.f_frsize * s.f_bavail) / 1024 ** 2) hardware_disk_space_total = str(disk_total) + " MB" hardware_disk_space_free = str(disk_free) + " MB" except: hardware_disk_space_total = "" hardware_disk_space_free = "" try: hardware_video_card = "" cards = [] p = subprocess.Popen(['lspci'], stdout=subprocess.PIPE) for line in p.stdout: if "VGA" in line: cards.append(line.split(":")[-1].strip()) p.wait() hardware_video_card = ", ".join(cards) except: hardware_video_card = "" # Get platform specific info for Windows elif sys.platform.startswith("win"): os_platform = "Windows" os_name = "Windows" os_details = platform.win32_ver()[2] os_version = platform.win32_ver()[1] try: hardware_cpu_details = str(_get_registry_value( "HKEY_LOCAL_MACHINE", "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString")) except: hardware_cpu_details = "" try: import ctypes class MEMORYSTATUSEX(ctypes.Structure): _fields_ = [("dwLength", ctypes.c_uint), ("dwMemoryLoad", ctypes.c_uint), ("ullTotalPhys", ctypes.c_ulonglong), ("ullAvailPhys", ctypes.c_ulonglong), ("ullTotalPageFile", ctypes.c_ulonglong), ("ullAvailPageFile", ctypes.c_ulonglong), ("ullTotalVirtual", ctypes.c_ulonglong), ("ullAvailVirtual", ctypes.c_ulonglong), ("sullAvailExtendedVirtual", ctypes.c_ulonglong), ] def __init__(self): # Initialize this to the size of MEMORYSTATUSEX self.dwLength = 2 * 4 + 7 * 8 # size = 2 ints, 7 longs return super(MEMORYSTATUSEX, self).__init__() stat = MEMORYSTATUSEX() ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)) hardware_memory_total = str(stat.ullTotalPhys / 1024 ** 2) + " MB" hardware_memory_free = str(stat.ullAvailPhys / 1024 ** 2) + " MB" except: hardware_memory_total = "" hardware_memory_free = "" try: current_folder = os.path.split(os.path.realpath(sys.argv[0]))[0] _, disk_total, disk_free = ctypes.c_int64(), ctypes.c_int64(), \ ctypes.c_int64() ret = ctypes.windll.kernel32.GetDiskFreeSpaceExW( unicode(current_folder), ctypes.byref(_), ctypes.byref(disk_total), ctypes.byref(disk_free)) hardware_disk_space_total = str(disk_total.value / 1024 ** 2) + " MB" hardware_disk_space_free = str(disk_free.value / 1024 ** 2) + " MB" except: hardware_disk_space_total = "" hardware_disk_space_free = "" try: tmp = str(_get_registry_value("HKEY_LOCAL_MACHINE", "HARDWARE\\DEVICEMAP\\VIDEO", "\Device\Video0")) idx = tmp.find("System") card = str(_get_registry_value("HKEY_LOCAL_MACHINE", tmp[idx:].replace("\\", "\\\\"), "Device Description")) hardware_video_card = card except: hardware_video_card = "" hardware_audio_card = "" # TODO # Get platform specific info for OS X elif sys.platform.startswith("darwin"): os_platform = "Darwin" os_name = "Mac OS X" os_details = platform.mac_ver()[1][1] os_version = platform.mac_ver()[0] try: proc = subprocess.Popen(['sysctl', '-a', 'hw.model'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) hardware_cpu_details = \ proc.stdout.readline().split(":")[1].strip() except: hardware_cpu_details = "" try: proc = subprocess.Popen(['sysctl', '-a', 'hw.memsize'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) tmp = int(proc.stdout.readline().split(":")[1].strip()) / 1024 ** 2 hardware_memory_total = str(tmp) + " MB" except: hardware_memory_total = "" try: import re proc = subprocess.Popen(['vm_stat'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) output = proc.stdout.read().split("\n") for item in output: x = item.find("Pages free:") y = item.find("page size of") if x > -1: non_decimal = re.compile(r'[^\d.]+') free = int(non_decimal.sub('', item)) if y > -1: non_decimal = re.compile(r'[^\d.]+') page = int(non_decimal.sub('', item)) hardware_memory_free = str((free * page) / 1024 ** 2) + "MB " except: hardware_memory_free = "" try: current_folder = os.path.split(os.path.realpath(sys.argv[0]))[0] s = os.statvfs(current_folder) disk_total = int((s.f_frsize * s.f_blocks) / 1024 ** 2) disk_free = int((s.f_frsize * s.f_bavail) / 1024 ** 2) hardware_disk_space_total = str(disk_total) + " MB" hardware_disk_space_free = str(disk_free) + " MB" except: hardware_disk_space_total = "" hardware_disk_space_free = "" try: proc = subprocess.Popen(['system_profiler', 'SPAudioDataType'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) hardware_audio_card = \ proc.stdout.read().split("\n")[2].strip(":").strip() except: hardware_audio_card = "" try: import plistlib proc = subprocess.Popen(['system_profiler', 'SPDisplaysDataType', '-xml'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) pl = plistlib.readPlist(proc.stdout) hardware_video_card = [] for card in pl[0]['_items']: hardware_video_card.append(card['sppci_model']) except: hardware_video_card = "" # Fill in info info["os_architecture"] = platform.architecture()[0] info["os_name"] = os_name info["os_details"] = os_details info["os_platform"] = os_platform info["os_release"] = platform.release() info["os_version"] = os_version info["settings_folder"] = expyriment._importer_functions.get_settings_folder() info["python_expyriment_build_date"] = __date__ info["python_expyriment_revision"] = __revision__ info["python_expyriment_version"] = __version__ if _numpy is not None: numpy_version = _numpy.version.version else: numpy_version = "" info["python_numpy_version"] = numpy_version if _pil is not None: pil_version = _pil.VERSION else: pil_version = "" info["python_pil_version"] = pil_version info["python_pygame_version"] = pygame.version.ver if _ogl is not None: pyopengl_version = _ogl.version.__version__ else: pyopengl_version = "" info["python_pyopengl_version"] = pyopengl_version if _parallel is not None: parallel_version = _parallel.VERSION else: parallel_version = "" info["python_pyparallel_version"] = parallel_version if _serial is not None: serial_version = _serial.VERSION else: serial_version = "" info["python_pyserial_version"] = serial_version info["python_version"] = "{0}.{1}.{2}".format(sys.version_info[0], sys.version_info[1], sys.version_info[2]) info["hardware_audio_card"] = hardware_audio_card info["hardware_cpu_architecture"] = platform.machine() info["hardware_cpu_details"] = hardware_cpu_details info["hardware_cpu_type"] = platform.processor() info["hardware_disk_space_free"] = hardware_disk_space_free info["hardware_disk_space_total"] = hardware_disk_space_total try: socket.gethostbyname("expyriment.googlecode.com") hardware_internet_connection = "Yes" except: hardware_internet_connection = "No" info["hardware_internet_connection"] = hardware_internet_connection info["hardware_memory_total"] = hardware_memory_total info["hardware_memory_free"] = hardware_memory_free info["hardware_ports_parallel"] = \ expyriment.io.ParallelPort.get_available_ports() info["hardware_ports_serial"] = \ expyriment.io.SerialPort.get_available_ports() info["hardware_video_card"] = hardware_video_card if as_string: sorted_keys = info.keys() sorted_keys.sort() rtn = "" for key in sorted_keys: tabs = "\t" * (4 - int((len(key) + 1) / 8)) try: rtn += key + ":" + tabs + info[key] + "\n" except TypeError: rtn += key + ":" + tabs + repr(info[key]) + "\n" else: rtn = info return rtn python-expyriment-0.7.0+git34-g55a4e7e/expyriment/cli.py0000644000175000017500000000714712314561273022337 0ustar oliveroliver#!/usr/bin/env python """A command line interface for Expyriment. Use this to start the Expyriment Reference Tool, or to run the Expyriment Test Suite or any Python script with predefined Expyriment default settings. Usage: python -m expyriment.cli [EXPYRIMENT SCRIPT] [OPTIONS] OPTIONS: -g No OpenGL -t No time stamps for output files -w Window mode -f Fast mode (no initialize delay and fast quitting) -a Auto create subject ID -i Intensive logging (log level 2) -d Develop mode (equivalent to -gwfat) -T Run the Expyriment Test Suite -A Start the Expyrimnent API Reference Tool -h Show this help """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' if __name__ == "__main__": import sys, os, subprocess import expyriment script = None if len(sys.argv) > 1: if sys.argv[1].endswith(".py"): script = sys.argv[1] for args in sys.argv[1:]: if args.startswith("-"): for arg in args[1:]: if arg == 'd': expyriment.control.set_develop_mode(True) elif arg == 'i': print "* Intensive logging" expyriment.io.defaults.event_logging = 2 elif arg == 'f': print "* Fast mode" expyriment.control.defaults.initialize_delay = 0 expyriment.control.defaults.fast_quit = False elif arg == 'w': print "* Window mode (No OpenGL)" expyriment.control.defaults.open_gl = False expyriment.control.defaults.window_mode = True elif arg == 'g': print "* No OpenGL" expyriment.control.defaults.open_gl = False elif arg == 't': print "* No time stamps" expyriment.io.defaults.outputfile_time_stamp = False elif arg == 'a': print "* Auto create subject id" expyriment.control.defaults.auto_create_subject_id = \ True elif arg == "T": print "Run Test Suite" expyriment.control.run_test_suite() sys.exit() elif arg == "A": print "Start API Reference Tool" expyriment.show_documentation(3) sys.exit() elif arg == 'h': print """ Usage: python -m expyriment.cli [EXPYRIMENT SCRIPT] [OPTIONS] OPTIONS: -g No OpenGL -t No time stamps for output files -w Window mode -f Fast mode (no initialize delay and fast quitting) -a Auto create subject ID -i Intensive logging (log level 2) -d Develop mode (equivalent to -gwfat) -T Run Test Suite -A Start API Reference Tool -h Show this help """ sys.exit() if script is not None: execfile(script) # FIXME cli.py in online docu? python-expyriment-0.7.0+git34-g55a4e7e/expyriment/_importer_functions.py0000644000175000017500000001212312314561273025646 0ustar oliveroliver""" Importer functions. This module contains helper function needed for importing plugins (extras) and for reading config file while import """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import sys try: import android except ImportError: android = None def import_plugins(init_filename): """Return the code to import all plugins of extra package as dict. Parameters ---------- init_filename : string fullpath to the __init__ file of the particular extra package Returns ------- out : string """ code = {} for filename in os.listdir(os.path.dirname(init_filename)): if filename.endswith(".py") and not (filename.startswith("__") or\ filename.endswith("defaults.py")): f = open(os.path.dirname(init_filename) + os.sep + filename) try: for line in f: if line[0:6] == "class ": tmp = line[6:].lstrip() name = tmp[:len(filename[:-4])] break code[filename] = "from {0} import {1}\n".format(filename[:-3], name) except: print("Warning: Could not import {0}!".format( os.path.dirname(init_filename) + os.sep + filename)) return code def import_plugin_defaults(init_filename): """Return the code to import all defaults of extra package as list. Parameters ---------- init_filename : string fullpath to the __init__ file of the particular extra package Returns ------- out : string """ # extra defaults code = [] for _filename in os.listdir(os.path.dirname(init_filename)): if _filename.endswith("_defaults.py"): code.append("execfile(r'{0}')\n".format(os.path.dirname( init_filename) + os.sep + os.sep + _filename)) return code def get_settings_folder(): """Return for expyriment setting folder in $HOME""" home = os.getenv('USERPROFILE') if home is None: if android is not None: home = "/storage/sdcard0/" else: home = os.getenv('HOME') for expy_homefolder in [".expyriment", "~expyriment"]: path = home + os.sep + expy_homefolder if os.path.isdir(path): return path return None def import_plugins_from_settings_folder(init_filename): """Return the code to import all plugins from the settings folder as dict. Includes the module folder in $home to the path. Returns ------- out : string """ module = init_filename.split(os.sep)[1] folder = get_settings_folder() if folder is None: return "" folder = folder + os.sep + module + os.sep code = {} sys.path.append(folder) try: for filename in os.listdir(os.path.dirname(folder)): if filename.endswith(".py") and\ not (filename.startswith("__") or\ filename.endswith("defaults.py")): f = open(os.path.dirname(folder) + os.sep + filename) try: for line in f: if line[0:6] == "class ": tmp = line[6:].lstrip() name = tmp[:len(filename[:-4])] break code[filename] = "from {0} import {1}\n".format(filename[:-3], name) print "import {0}.extras.{1} (from homefolder)".format( module, name) except: print("Could not import {0}!".format( os.path.dirname(folder) + os.sep + filename)) except: pass return code def import_plugin_defaults_from_home(init_filename): """Return the code to import all defaults of extra package as list. Parameters ---------- init_filename : string fullpath to the __init__ file of the particular extra package """ module = init_filename.split(os.sep)[1] folder = get_settings_folder() if folder is None: return "" folder = folder + os.sep + module + os.sep code = [] try: for _filename in os.listdir(os.path.dirname(folder)): if _filename.endswith("_defaults.py"): code.append("execfile(r'{0}')\n".format(os.path.dirname( folder) + os.sep + os.sep + _filename)) except: pass return code def post_import_hook(): """Execute post import file.""" home = get_settings_folder() if home is None: return "" filename = home + os.sep + "post_import.py" if os.path.isfile(filename): print "process {0}".format(filename) return "execfile(r'{0}')\n".format(filename) else: return "" python-expyriment-0.7.0+git34-g55a4e7e/expyriment/control/0000775000175000017500000000000012314561273022667 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/control/_experiment_control.py0000644000175000017500000004436112314561273027326 0ustar oliveroliver""" The control._experiment_control module of expyriment. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import sys import os import pygame try: import android except ImportError: android = None try: import android.mixer as mixer except ImportError: import pygame.mixer as mixer import defaults import expyriment from expyriment import design, stimuli, misc from expyriment.io import DataFile, EventFile, TextInput, Keyboard, Mouse from expyriment.io import _keyboard, TouchScreenButtonBox from expyriment.io._screen import Screen from _miscellaneous import _set_stdout_logging, is_idle_running from expyriment.misc import unicode2str, constants def start(experiment=None, auto_create_subject_id=None, subject_id=None, skip_ready_screen=False): """Start an experiment. This starts an experiment defined by 'experiment' and asks for the subject number. When the subject number is entered and confirmed by ENTER, a data file is created. Eventually, "Ready" will be shown on the screen and the method waits for ENTER to be pressed. After experiment start the following additional properties are available: * experiment.subject -- the current subject id * experiment.data -- the main data file Parameters ---------- experiment : design.Experiment, optional (DEPRECATED) Don't use this parameter, it only exists to keep backward compatibility. auto_create_subject_id : bool, optional if True new subject id will be created automatically. subject_id : integer, optional start with a specific subject_id. No subject id input mask will be presented. Subject_id must be an integer. Setting this paramter overrules auto_create_subject_id. skip_ready_screen : boolen, optional if True ready screen will be skipped. default=False Returns ------- exp : design.Experiment The started experiment will be returned. """ if experiment is None: experiment = expyriment._active_exp if experiment != expyriment._active_exp: raise Exception("Experiment is not the currently initialized " + "experiment!") if experiment.is_started: raise Exception("Experiment is already started!") if subject_id is not None: if not isinstance(subject_id, int): raise Exception("Subject id must be an integer. " + "{0} is not allowed.".format(type(subject_id))) auto_create_subject_id = True elif auto_create_subject_id is None: auto_create_subject_id = defaults.auto_create_subject_id experiment._is_started = True # temporarily switch off log_level old_logging = experiment.log_level experiment.set_log_level(0) screen_colour = experiment.screen.colour experiment._screen.colour = [0, 0, 0] if subject_id is None: default_number = DataFile.get_next_subject_number() else: default_number = subject_id if not auto_create_subject_id: if android is not None: background_stimulus = stimuli.BlankScreen(colour=(0, 0, 0)) fields = [stimuli.Circle(diameter=200, colour=(70, 70, 70), position=(0, 70)), stimuli.Circle(diameter=200, colour=(70, 70, 70), position=(0, -70)), stimuli.Rectangle(size=(50, 50), colour=(70, 70, 70), position=(120, 0))] fields[0].scale((0.25, 0.25)) fields[1].scale((0.25, 0.25)) plusminus = [ stimuli.TextLine("Subject Number:", text_size=24, text_colour=constants.C_EXPYRIMENT_PURPLE, position=(-182, 0)), stimuli.TextLine(text="+", text_size=36, position=(0, 70), text_font="FreeMono", text_colour=(0, 0, 0)), stimuli.TextLine(text="-", text_size=36, position=(0, -70), text_font="FreeMono", text_colour=(0, 0, 0)), stimuli.TextLine(text = "Go", text_size=18, position=(120, 0), text_colour=(0, 0, 0))] subject_id = default_number while True: text = stimuli.TextLine( text="{0}".format(subject_id), text_size=28, text_colour=constants.C_EXPYRIMENT_ORANGE) btn = TouchScreenButtonBox( button_fields=fields, stimuli=plusminus+[text], background_stimulus=background_stimulus) btn.show() key, rt = btn.wait() if key == fields[0]: subject_id += 1 elif key == fields[1]: subject_id -= 1 if subject_id <= 0: subject_id = 0 elif key == fields[2]: break experiment._subject = int(subject_id) else: position = (0, 0) while True: ask_for_subject = TextInput( message="Subject Number:", position=position, message_colour=misc.constants.C_EXPYRIMENT_PURPLE, message_text_size=24, user_text_colour=misc.constants.C_EXPYRIMENT_ORANGE, user_text_size=20, background_colour=(0, 0, 0), frame_colour=(70, 70, 70), ascii_filter=misc.constants.K_ALL_DIGITS) subject_id = ask_for_subject.get(repr(default_number)) try: experiment._subject = int(subject_id) break except: pass else: experiment._subject = default_number experiment.screen.clear() experiment.screen.update() experiment._data = DataFile(additional_suffix=experiment.filename_suffix) experiment.data.add_variable_names(experiment.data_variable_names) for txt in experiment.experiment_info: experiment.data.add_experiment_info(txt) for line in experiment.__str__().splitlines(): experiment.data.add_experiment_info(line) for f in experiment.bws_factor_names: _permuted_bws_factor_condition = \ experiment.get_permuted_bws_factor_condition(f) if isinstance(_permuted_bws_factor_condition, unicode): _permuted_bws_factor_condition = \ unicode2str(_permuted_bws_factor_condition) experiment.data.add_subject_info("{0} = {1}".format( unicode2str(f), _permuted_bws_factor_condition)) if experiment.events is not None: experiment.events._time_stamp = experiment.data._time_stamp experiment.events.rename(experiment.events.standard_file_name) number = defaults.initialize_delay - int(experiment.clock.time / 1000) if number > 0: text = stimuli.TextLine("Initializing, please wait...", text_size=24, text_colour=(160, 70, 250), position=(0, 0)) stimuli._stimulus.Stimulus._id_counter -= 1 text.present() text.present() # for flipping with double buffer text.present() # for flipping with tripple buffer while number > 0: counter = stimuli.TextLine( "{num:02d}".format(num=number), text_font='FreeMono', text_size=18, text_bold=True, text_colour=misc.constants.C_EXPYRIMENT_ORANGE, position=(0, -50), background_colour=(0, 0, 0)) stimuli._stimulus.Stimulus._id_counter -= 1 counter.present(clear=False) number -= 1 key = experiment.keyboard.wait(pygame.K_ESCAPE, duration=1000, check_for_control_keys=False) if key[0] is not None: break position = (0, 0) if not skip_ready_screen: stimuli.TextLine("Ready", position=position, text_size=24, text_colour=misc.constants.C_EXPYRIMENT_ORANGE).present() stimuli._stimulus.Stimulus._id_counter -= 1 if android is None: experiment.keyboard.wait() else: experiment.mouse.wait_press() experiment.set_log_level(old_logging) experiment._screen.colour = screen_colour experiment.log_design_to_event_file() experiment._event_file_log("Experiment,started") return experiment def pause(): """Pause a running experiment. This will show a pause screen and waits for ENTER to be pressed to continue. """ if not expyriment._active_exp.is_initialized: raise Exception("Experiment is not initialized!") experiment = expyriment._active_exp experiment._event_file_log("Experiment,paused") screen_colour = experiment.screen.colour experiment._screen.colour = [0, 0, 0] old_logging = experiment.log_level experiment.set_log_level(0) if android is not None: position = (0, 200) else: position = (0, 0) stimuli.TextLine("Paused", position=position, text_colour=misc.constants.C_EXPYRIMENT_ORANGE, text_size=24).present() experiment.set_log_level(old_logging) experiment._screen.colour = screen_colour stimuli._stimulus.Stimulus._id_counter -= 1 misc.Clock().wait(200) if android is None: experiment.keyboard.wait() else: experiment.mouse.wait_press() experiment._event_file_log("Experiment,resumed") return key def end(goodbye_text=None, goodbye_delay=None, confirmation=False, fast_quit=None, system_exit=False): """End expyriment. Parameters ---------- goodbye_text : str, obligatory text to present on the screen when quitting goodbye_delay : int, optional period to show the goodbye_text confirmation : bool, optional ask for confirmation (default = False) fast_quit : bool, optional quit faster by hiding the screen before actually quitting (default = None) system_exit : bool, optional call Python's sys.exit() method when ending expyriment (default = False) Returns ------- out : bool True if Expyriment (incl. Pygame) has been quit. """ if not expyriment._active_exp.is_initialized: pygame.quit() if system_exit: sys.exit() return True experiment = expyriment._active_exp if confirmation: experiment._event_file_log("Experiment,paused") screen_colour = experiment.screen.colour experiment._screen.colour = [0, 0, 0] if android is not None: position = (0, 200) else: position = (0, 0) stimuli.TextLine("Quitting Experiment? (y/n)", position=position, text_colour=misc.constants.C_EXPYRIMENT_ORANGE, text_size=24).present() stimuli._stimulus.Stimulus._id_counter -= 1 char = Keyboard().wait_char(["y", "n"], check_for_control_keys=False) if char[0] == "n": experiment._screen.colour = screen_colour experiment._event_file_log("Experiment,resumed") return False experiment._event_file_log("Experiment,ended") if goodbye_text is None: goodbye_text = defaults.goodbye_text if goodbye_delay is None: goodbye_delay = defaults.goodbye_delay if experiment.events is not None: experiment.events.save() if experiment.data is not None: experiment.data.save() if fast_quit is None: fast_quit = defaults.fast_quit if fast_quit and experiment.is_started: if experiment.screen.window_mode: pygame.display.set_mode(experiment.screen._window_size) pygame.display.iconify() experiment._screen.colour = [0, 0, 0] stimuli.TextLine(goodbye_text, position=(0, 0), text_colour=misc.constants.C_EXPYRIMENT_PURPLE, text_size=24).present() stimuli._stimulus.Stimulus._id_counter -= 1 if not fast_quit: misc.Clock().wait(goodbye_delay) expyriment._active_exp = design.Experiment("None") pygame.quit() return True def initialize(experiment=None): """Initialize an experiment. This initializes an experiment defined by 'experiment' as well as the underlying expyriment system. If 'experiment' is None, a new Experiment object will be created and returned. Furthermore, a screen, a clock, a keyboard and a event file are created and added to the experiment. The initialization screen is shown for a short delay to ensure that Python is fully initialized and time accurate. Afterwards, "Preparing experiment..." is presented on the screen. After experiment initialize the following additional properties are available: - experiment.screen -- the current screen - experiment.clock -- the main clock - experiment.keyboard -- the main keyboard - experiment.mouse -- the main mouse - experiment.event -- the main event file Parameters ---------- experiment : design.Experiment, optional the experiment to initialize Returns ------- exp : design.Experiment initialized experiment """ if experiment is None: experiment = design.Experiment() stdout_logging = defaults.stdout_logging expyriment._active_exp = experiment experiment._log_level = 0 # switch off for the first screens _keyboard.quit_key = defaults.quit_key _keyboard.pause_key = defaults.pause_key _keyboard.end_function = end _keyboard.pause_function = pause mixer.pre_init(defaults.audiosystem_sample_rate, defaults.audiosystem_bit_depth, defaults.audiosystem_channels, defaults.audiosystem_buffer_size) if defaults.audiosystem_autostart: mixer.init() mixer.init() # Needed on some systems experiment._clock = misc.Clock() experiment._screen = Screen((0, 0, 0), defaults.open_gl, defaults.window_mode, defaults.window_size) # Hack for IDLE: quit pygame and call atexit functions when crashing if is_idle_running() and sys.argv[0] != "": try: import idlelib.run def wrap(orig_func): def newfunc(*a, **kw): pygame.quit() import atexit atexit._run_exitfuncs() idlelib.run.flush_stdout = orig_func return orig_func(*a, **kw) return newfunc idlelib.run.flush_stdout = wrap(idlelib.run.flush_stdout) except ImportError: pass experiment._data = None experiment._subject = None experiment._is_initialized = True # required before EventFile if defaults.event_logging > 0: experiment._events = EventFile( additional_suffix=experiment.filename_suffix, time_stamp=True) if stdout_logging: _set_stdout_logging(experiment._events) else: experiment._events = None experiment._keyboard = Keyboard() experiment._mouse = Mouse(show_cursor=False) logo = stimuli.Picture(misc.constants.EXPYRIMENT_LOGO_FILE, position=(0, 100)) logo.scale((0.7, 0.7)) text = stimuli.TextLine("Version {0}".format(expyriment.get_version()), text_size=14, text_colour=misc.constants.C_EXPYRIMENT_ORANGE, background_colour=(0, 0, 0), position=(0, 40)) canvas = stimuli.Canvas((600, 300), colour=(0, 0, 0)) canvas2 = stimuli.Canvas((600, 300), colour=(0, 0, 0)) logo.plot(canvas) text.plot(canvas) hash_ = expyriment.get_experiment_secure_hash() if hash_ is not None: text2 = stimuli.TextLine( "{0} ({1})".format(os.path.split(sys.argv[0])[1], hash_), text_size=14, text_colour=misc.constants.C_EXPYRIMENT_ORANGE, background_colour=(0, 0, 0), position=(0, 10)) text2.plot(canvas) canvas.preload(True) canvas._set_surface(canvas._get_surface().convert()) start = experiment.clock.time r = [x for x in range(256) if x % 5 == 0] stopped = False if defaults.initialize_delay > 0: for x in r: canvas._get_surface().set_alpha(x) canvas2.clear_surface() canvas.plot(canvas2) canvas2.present() experiment.clock.wait(1) key = experiment.keyboard.check(pygame.K_ESCAPE, check_for_control_keys=False) if key is not None: stopped = True break duration = experiment.clock.time - start if duration < 2000 and not stopped: start = experiment.clock.time while experiment.clock.time - start < 2000: key = experiment.keyboard.check(pygame.K_ESCAPE, check_for_control_keys=False) if key is not None: stopped = True break r = [x for x in range(256)[::-1] if x % 5 == 0] if not stopped: for x in r: canvas._get_surface().set_alpha(x) canvas2.clear_surface() canvas.plot(canvas2) canvas2.present() experiment.clock.wait(1) key = experiment.keyboard.check(pygame.K_ESCAPE, check_for_control_keys=False) if key is not None: break stimuli.TextLine("Preparing experiment...", text_size=24, text_colour=misc.constants.C_EXPYRIMENT_PURPLE).present() experiment._screen.colour = experiment.background_colour experiment.set_log_level(defaults.event_logging) stimuli._stimulus.Stimulus._id_counter = 0 return experiment python-expyriment-0.7.0+git34-g55a4e7e/expyriment/control/_test_suite.py0000644000175000017500000004201212314561273025565 0ustar oliveroliver# -*- coding: utf-8 -*- """The expyriment testsuite. This module contains several functions to test the machine expyriment is running on. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import pygame try: import OpenGL.GL as ogl except: ogl = None import defaults import expyriment from expyriment import stimuli, io from expyriment.misc import constants, statistics from expyriment.misc._timer import get_time def _make_graph(x, y, colour): """Make the graph.""" graph = stimuli.Canvas(size=(max(x) * 3 + 10, max(y) * 3 + 10)) for counter in range(len(x)): dot = stimuli.Dot(radius=1, colour=colour) dot.position = (x[counter] * 3 - graph.size[0] / 2 + 5, y[counter] * 3 - graph.size[1] / 2 + 5) dot.plot(graph) return graph def _stimulus_timing(exp): """Test the timing of stimulus presentation.""" def _test1(): info = """This will test if stimuli can be presented timing accurately. The left picture shows a good result, the right picture shows a bad result. [Press RETURN to continue]""" text = stimuli.TextScreen("Stimulus presentation test (1)", info) y = [] for x in [16, 32, 48, 64]: y.extend([x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, ]) graph1 = _make_graph(range(60), y, [0, 255, 0]) y = range(80) graph2 = _make_graph(range(60), y, [255, 0, 0]) graph1.position = (-200, -100) graph2.position = (200, -100) text.present(update=False) graph1.present(clear=False, update=False) graph2.present(clear=False) exp.keyboard.wait([constants.K_RETURN]) exp.screen.clear() exp.screen.update() cnvs = stimuli.FixCross(cross_size=200, line_width=3) picture = stimuli.Rectangle((100, 100)) cnvs.preload() picture.preload() todo_time = [] actual_time = [] for x in range(200): todo_time.append(expyriment.design.randomize.rand_int(0, 60)) picture.present() start = exp.clock.time exp.clock.wait(todo_time[-1]) cnvs.present() actual_time.append(exp.clock.time - start) exp.clock.wait(expyriment.design.randomize.rand_int(30, 100)) text = stimuli.TextScreen("Results", "[Press RETURN to continue]") graph = _make_graph(todo_time, actual_time, [150, 150, 150]) graph.position = (0, -100) text.present(update=False) graph.present(clear=False) exp.keyboard.wait([constants.K_RETURN]) text = stimuli.TextScreen( "Which picture looks most similar to the results?", "[Press LEFT or RIGHT arrow key]") y = [] for x in [16, 32, 48, 64]: y.extend([x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, ]) graph1 = _make_graph(range(60), y, [0, 255, 0]) y = range(80) graph2 = _make_graph(range(60), y, [255, 0, 0]) graph1.position = (-200, -100) graph2.position = (200, -100) text.present(update=False) graph1.present(clear=False, update=False) graph2.present(clear=False) key, _rt = exp.keyboard.wait([constants.K_LEFT, constants.K_RIGHT]) if key == constants.K_LEFT: response1 = "Steps" elif key == constants.K_RIGHT: response1 = "Line" return todo_time, actual_time, response1 def _test2(): info = """This will test if stimulus presentation can be synchronized to the refreshrate of the screen. A good result is a fast, constant and smooth flickering without any distortions (e.g. horizontal stripes, tearing). The estimated refreshrate should resemble your actual screen refreshrate (common refreshrates are between 40 and 240 Hz). [Press RETURN to continue]""" text = stimuli.TextScreen("Stimulus presentation test (2)", info) text.present() exp.keyboard.wait([constants.K_RETURN]) black = stimuli.BlankScreen(colour=constants.C_BLACK) black.preload() white = stimuli.BlankScreen(colour=constants.C_WHITE) white.preload() times = [] black.present() for _x in range(100): start = get_time() black.present() times.append(get_time() - start) start = get_time() white.present() times.append(get_time() - start) refresh_rate = 1000 / (statistics.mean(times) * 1000) info = """Your estimated refresh rate is {0} Hz. [Press RETURN to continue] """.format(refresh_rate) text = stimuli.TextScreen("Results", info) text.present() exp.keyboard.wait([constants.K_RETURN]) text = stimuli.TextScreen( "Was the flickering fast, constant and smooth, without any distortions?", "[Press Y or N]") text.present() key, _rt = exp.keyboard.wait([constants.K_y, constants.K_n]) if key == constants.K_y: response2 = "Yes" elif key == constants.K_n: response2 = "No" return refresh_rate, response2 def _test3(): info = """This will test the video card's settings for multiple buffers and page flipping. If none of the following squares are constantly blinking, page flipping is not activated and buffer contents are copied. If only the left square is constantly blinking, page flipping is activated and a double buffer is used. If additionally the right square is constantly blinking, page flipping is activated and a tripple buffer is used. [Press RETURN to continue]""" text = stimuli.TextScreen("Stimulus presentation test (3)", info) text.present() exp.keyboard.wait([constants.K_RETURN]) c1 = stimuli.Canvas((400, 400)) c2 = stimuli.Canvas((400, 400)) c3 = stimuli.Canvas((400, 400)) frame1 = stimuli.Rectangle((100, 100), position=(-100, 0)) frame2 = stimuli.Rectangle((100, 100), position=(100, 0)) bg = stimuli.Rectangle((90, 90), colour=exp.background_colour) bg.plot(frame1) bg.plot(frame2) frame1.plot(c1) frame2.plot(c2) frame1.plot(c3) frame2.plot(c3) c1.preload() c2.preload() c3.preload() c1.present() c2.present() c3.present() for _x in range(50): d = stimuli.Dot(1, colour=(1, 1, 1)) d.present(clear=False) exp.clock.wait(100) text = stimuli.TextScreen( "How many squares were constantly blinking?", "[Press 0, 1 or 2]") text.present() key, _rt = exp.keyboard.wait([constants.K_0, constants.K_1, constants.K_2]) if key == constants.K_0: response3 = 0 elif key == constants.K_1: response3 = 1 elif key == constants.K_2: response3 = 2 return (response3,) return _test1() + _test2() + _test3() def _audio_playback(exp): """Test the audio playback""" info = """This will test the audio playback. A test tone will be played. [Press RETURN to continue] """ text = stimuli.TextScreen("Audio playback test", info) text.present() exp.keyboard.wait([constants.K_RETURN]) exp.screen.clear() exp.screen.update() a = stimuli.Tone(duration=1000) a.present() exp.clock.wait(1000) text = stimuli.TextScreen("Did you hear the tone?", "[Press Y or N]") text.present() key, _rt = exp.keyboard.wait([constants.K_y, constants.K_n]) if key == constants.K_y: response = "Yes" elif key == constants.K_n: response = "No" return response def _font_viewer(exp): all_fonts = expyriment.misc.list_fonts().keys() def info_screen(): stimuli.TextScreen(heading="Expyriment Font Viewer", text=""" arrow keys left/right -- Switch font type arrow keys up/down -- Switch font size i -- Switch italic b -- Switch bold c -- Change text h -- Help return -- Quit [Touch screen] click left/right side -- Switch font type click up/down side -- Switch font size click center -- Quit """, text_font="freemono", text_bold=True, text_justification=0).present() exp.keyboard.wait() default_text = u"""The quick brown fox jumps over the lazy dog. ABCDEFGHIJKLMNOPQRSTUVWXYZ ÄÖÜ abcdefghijklmnopqrstuvwxyz äöü 1234567890.:,;ßéèê(*!?')""" text = default_text size = 14 font_id = 0 italic = False bold = False quest = io.TextInput(message="Please enter text: (Keep empty for default text)", length=35) mouse = io.Mouse(show_cursor=True) bs = (exp.screen.size[0] / 3.5, exp.screen.size[1] / 3.5) # rects center, left, right, top, button] cl = (20, 20, 20) rects = [stimuli.Rectangle(size=bs, position=[0, 0], colour=cl), stimuli.Rectangle(size=bs, position=[(bs[0] - exp.screen.size[0]) / 2.2, 0], colour=cl), stimuli.Rectangle(size=bs, position=[(exp.screen.size[0] - bs[0]) / 2.2, 0], colour=cl), stimuli.Rectangle(size=bs, position=[0, (bs[1] - exp.screen.size[1]) / 2.2], colour=cl), stimuli.Rectangle(size=bs, position=[0, (exp.screen.size[1] - bs [1]) / 2.2], colour=cl)] rect_key_mapping = [constants.K_RETURN, constants.K_LEFT, constants.K_RIGHT, constants.K_UP, constants.K_DOWN] info_screen() while True: font_str = all_fonts[font_id] font_description = "font '{0}', size {1}".format(font_str, size) if italic: font_description += ", italic" if bold: font_description += ", bold" canvas = stimuli.BlankScreen() for r in rects: r.plot(canvas) try: stimuli.TextScreen( heading=font_description, text=text, text_font=font_str, text_size=size, text_justification=0, text_italic=italic, text_bold=bold, text_colour=(255, 255, 255)).plot(canvas) except: stimuli.TextLine(text="Sorry, I can't display the text with " + "{0}".format(font_description), text_colour=constants.C_EXPYRIMENT_ORANGE).plot(canvas) canvas.present() mouse.clear() exp.keyboard.clear() while True: key = exp.keyboard.check() if mouse.get_last_button_down_event() is not None: for cnt, r in enumerate(rects): if r.overlapping_with_position(mouse.position): key = rect_key_mapping[cnt] break if key is not None: break if (key == constants.K_RETURN): break elif key == constants.K_UP: size += 2 elif key == constants.K_DOWN: size -= 2 elif key == constants.K_LEFT: font_id -= 1 if font_id < 0: font_id = len(all_fonts) - 1 elif key == constants.K_RIGHT: font_id += 1 if font_id >= len(all_fonts): font_id = 0 elif key == constants.K_i: italic = not(italic) elif key == constants.K_b: bold = not(bold) elif key == constants.K_c: text = quest.get() if len(text) <= 0: text = default_text else: info_screen() mouse.hide_cursor() def _write_protocol(exp, results): """Write a protocol with all test results.""" sorted_keys = results.keys() sorted_keys.sort() rtn = "" for key in sorted_keys: tabs = "\t" * (4 - int((len(key) + 1) / 8)) + "\t" try: rtn += key + ":" + tabs + results[key] + "\n" except TypeError: rtn += key + ":" + tabs + repr(results[key]) + "\n" filename = os.path.join(os.getcwd(), "test_suite_protocol.xpp") with open(filename, 'w') as f: f.write(rtn) text = stimuli.TextScreen( "Saved as", '"' + filename + '"' + "\n\n[Press RETURN to continue]") text.present() exp.keyboard.wait(constants.K_RETURN) return [] # required for event loop def _find_self_tests(): classes = [] method = [] rtn = [] for module in ["expyriment.io", "expyriment.io.extras"]: exec("classes = dir({0})".format(module)) for cl in classes: if not cl.startswith("_"): exec("method = dir({0}.{1})".format(module, cl)) if "_self_test" in method: rtn.append([module, cl]) return rtn def run_test_suite(): """Run the Expyriment test suite.""" quit_experiment = False if not expyriment._active_exp.is_initialized: defaults.initialize_delay = 0 defaults.event_logging = 0 exp = expyriment.control.initialize() quit_experiment = True else: exp = expyriment._active_exp # make menu and code for test functions test_functions = ['', '', ''] menu = ["1) Visual stimulus presentation", "2) Auditory stimulus presentation", "3) Font Viewer"] for mod, cl in _find_self_tests(): test_functions.append("rtn = {0}.{1}._self_test(exp)".format(mod, cl)) menu.append("{0}) {1} test".format(len(test_functions), cl)) menu.append("{0}) Write protocol".format(len(test_functions) + 1)) menu.append("{0}) Quit".format(len(test_functions) + 2)) test_functions.extend(['rtn = _write_protocol(exp, results)', 'go_on=False;rtn=[];']) background = stimuli.Canvas(size=[400, 600]) pict = stimuli.Picture(constants.EXPYRIMENT_LOGO_FILE, position=(0, 200)) pict.scale(0.5) pict.plot(background) results = expyriment.get_system_info() try: import android mouse = expyriment.io.Mouse(show_cursor=False) except ImportError: android = None mouse = None preselected_item = 0 go_on = True while go_on: select = expyriment.io.TextMenu( "Test suite", menu, width=350, justification=0, background_stimulus=background, mouse=mouse).get(preselected_item) if select == 0: rtn = _stimulus_timing(exp) results["testsuite_visual_timing_todo"] = rtn[0] results["testsuite_visual_timing_actual"] = rtn[1] results["testsuite_visual_timing_user"] = rtn[2] results["testsuite_visual_sync_refresh_rate"] = str(rtn[3]) + " Hz" results["testsuite_visual_sync_user"] = rtn[4] results["testsuite_visual_flipping_user"] = rtn[5] if ogl is not None: results["testsuite_visual_opengl_vendor"] = ogl.glGetString(ogl.GL_VENDOR) results["testsuite_visual_opengl_renderer"] = ogl.glGetString(ogl.GL_RENDERER) results["testsuite_visual_opengl_version"] = ogl.glGetString(ogl.GL_VERSION) results["testsuite_visual_pygame_driver"] = pygame.display.get_driver() extensions = ogl.glGetString(ogl.GL_EXTENSIONS).split(" ") if extensions[-1] == "": extensions = extensions[:-1] results["testsuite_visual_opengl_extensions"] = extensions else: results["testsuite_visual_opengl_vendor"] = "" results["testsuite_visual_opengl_renderer"] = "" results["testsuite_visual_opengl_version"] = "" results["testsuite_visual_pygame_driver"] = "" results["testsuite_visual_opengl_extensions"] = "" results["testsuite_visual_pygame_driver"] = pygame.display.get_driver() results["testsuite_visual_pygame_screensize"] = exp.screen.size preselected_item = select + 1 elif select == 1: results["testsuite_audio_user"] = _audio_playback(exp) try: results["testsuite_audio_frequency"] = str(pygame.mixer.get_init()[0]) + " Hz" results["testsuite_audio_bitdepth"] = str(abs(pygame.mixer.get_init()[1])) + " bit" results["testsuite_audio_channels"] = pygame.mixer.get_init()[2] except: presults["testsuite_audio_frequency"] = "" results["testsuite_audio_bitdepth"] = "" results["testsuite_audio_channels"] = "" preselected_item = select + 1 elif select == 2: _font_viewer(exp) preselected_item = select + 1 else: exec(test_functions[select]) results.update(rtn) preselected_item = select + 1 if quit_experiment: expyriment.control.end(goodbye_delay=0, goodbye_text="Quitting test suite") python-expyriment-0.7.0+git34-g55a4e7e/expyriment/control/_miscellaneous.py0000644000175000017500000002132112314561273026240 0ustar oliveroliver""" The control._miscellaneous module of expyriment. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import sys import pygame import defaults import expyriment from expyriment.control import defaults as control_defaults def start_audiosystem(): """Start the audio system. NOTE: The audiosystem is automatically started when initializing an Experiment! """ pygame.mixer.init() def stop_audiosystem(): """Stop the audio system.""" pygame.mixer.quit() def get_audiosystem_is_playing(channel=None): """Check if the audiosystem is busy playing sounds. Parameters ---------- channel : pygame.mixer.Channel, optional specific channel to check Returns ------- out : bool Returns True if any sound is playing. """ if channel is None: rtn = pygame.mixer.get_busy() else: rtn = channel.get_busy() if rtn == 0: rtn = False elif rtn > 0: rtn = True return rtn def wait_end_audiosystem(channel=None): """Wait until audiosystem has ended playing sounds. Blocks until the audiosystem is not busy anymore and only returns then. Parameters ---------- channel : pygame.mixer.Channel, optional specific channel to wait for end of playing """ while get_audiosystem_is_playing(channel): for event in pygame.event.get(pygame.KEYDOWN): if event.type == pygame.KEYDOWN and \ (event.key == control_defaults.quit_key or event.key == control_defaults.pause_key): if channel is None: pygame.mixer.stop() else: channel.stop() expyriment.io.Keyboard.process_control_keys(event) def set_develop_mode(onoff, intensive_logging=False): """Set defaults for a more convenient develop mode. Notes ----- The function set the following global variables >>> expyriment.control.defaults.initialize_delay = 0 >>> expyriment.control.defaults.window_mode = True >>> expyriment.control.defaults.fast_quit = True >>> expyriment.control.defaults.auto_create_subject_id = True >>> expyriment.io.defaults.outputfile_time_stamp = False Parameters ---------- onoff : bool set develop_mode on (True) or off (False) intensive_logging : bool, optional True sets expyriment.io.defaults.event_logging=2 (default = False) """ if onoff: defaults._mode_settings = [defaults.initialize_delay, defaults.window_mode, defaults.fast_quit, expyriment.io.defaults.outputfile_time_stamp, defaults.auto_create_subject_id] print "*** DEVELOP MODE ***" defaults.initialize_delay = 0 defaults.window_mode = True defaults.fast_quit = True expyriment.io.defaults.outputfile_time_stamp = False defaults.auto_create_subject_id = True else: print "*** NORMAL MODE ***" if defaults._mode_settings is not None: defaults.initialize_delay = defaults._mode_settings[0] defaults.window_mode = defaults._mode_settings[1] defaults.fast_quit = defaults._mode_settings[2] expyriment.io.defaults.outputfile_time_stamp = \ defaults._mode_settings[3] defaults.auto_create_subject_id = defaults._mode_settings[4] defaults._mode_settings = None else: pass # Nothing to do if intensive_logging: expyriment.control.defaults.event_logging = 2 def _get_module_values(goal_dict, module): value = None for var in dir(module): if not var.startswith("_"): exec("value = {0}.{1}".format(module.__name__, var)) goal_dict["{0}.{1}".format(module.__name__, var)] = value return goal_dict def get_defaults(search_str="", as_string=False): """Return a dictionary with all default values in the current Expyriment environment. The keys represent the variables names. Parameters ---------- search_str : str, optional search for a specific expression as_string : bool, optional print as string instead of dict """ defaults = {} defaults = _get_module_values(defaults, expyriment.design.defaults) defaults = _get_module_values(defaults, expyriment.control.defaults) defaults = _get_module_values(defaults, expyriment.stimuli.defaults) defaults = _get_module_values(defaults, expyriment.io.defaults) defaults = _get_module_values(defaults, expyriment.misc.defaults) defaults = _get_module_values(defaults, expyriment.design.extras.defaults) defaults = _get_module_values(defaults, expyriment.stimuli.extras.defaults) defaults = _get_module_values(defaults, expyriment.io.extras.defaults) defaults = _get_module_values(defaults, expyriment.misc.extras.defaults) if len(search_str) >= 0: tmp = {} for key in defaults.keys(): if key.lower().find(search_str.lower()) >= 0: tmp[key] = defaults[key] defaults = tmp if as_string: sorted_keys = defaults.keys() sorted_keys.sort() rtn = "" for key in sorted_keys: tabs = "\t" * (4 - int((len(key) + 1) / 8)) rtn += key + ":" + tabs + repr(defaults[key]) + "\n" else: rtn = defaults return rtn def register_wait_callback_function(function, exp=None): """Register a wait callback function. The registered wait callback function will be repetitively executed in all Expyriment wait and event loops that wait for an external input. That is, they are executed by the following functions (at least once!): control.wait_end_audiosystem, misc.clock.wait, misc.clock.wait_seconds, misc.clock.wait_minutes io.keyboard.wait, io.keyboard.wait_char, io.buttonbox.wait, io.gamepad.wait_press, io.triggerinput.wait, io.mouse.wait_press, io.serialport.read_line, io.textinput.get, stimulus.video.wait_frame, stimulus.video.wait_end Parameters ---------- function : function the wait function exp : design.Experiment, optional specific experiment for which to register wait function Notes ----- CAUTION! If wait callback function takes longer than 1 ms to process, Expyriment timing will be affected! """ if exp is not None: exp.register_wait_callback_function(function) else: expyriment._active_exp.register_wait_callback_function(function) def unregister_wait_callback_function(exp=None): """Unregister wait function. Parameters ---------- exp : design.Experiment, optional specific experiment for which to unregister wait function """ if exp is not None: exp.unregister_wait_callback_function() else: expyriment._active_exp.unregister_wait_callback_function() def is_ipython_running(): """Return True if IPython is running.""" try: __IPYTHON__ return True except NameError: return False def is_idle_running(): """Return True if IDLE is running.""" return "idlelib.run" in sys.modules def _set_stdout_logging(event_file): """Set logging of stdout and stderr to event file. Note that if the script is called via IPython or IDLE logging will not work. Parameters ---------- event_file : io.EventFile the event file """ class Logger(object): def __init__(self, event_file, log_tag): self.terminal = sys.stdout self.event_file = event_file self.tag = log_tag self._buffer = [] def write(self, message): self.terminal.write(message) self._buffer.append(message) if message.endswith("\n"): tmp = "".join(self._buffer).strip("\n") self.event_file.log("{0},received,{1}".format(self.tag, repr(tmp))) self._buffer = [] def flush(self): # required for some modules (e.g. multiprocessing) pass if is_ipython_running(): print "Standard output and error logging is switched off under IPython." elif is_idle_running(): print "Standard output and error logging is switched off under IDLE." else: sys.stderr = Logger(event_file, "stderr") sys.stdout = Logger(event_file, "stdout") python-expyriment-0.7.0+git34-g55a4e7e/expyriment/control/defaults.py0000644000175000017500000000177712314561273025062 0ustar oliveroliver""" Default settings for the control package. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from expyriment.misc import constants as _constants initialize_delay = 10 # After approximately 10 seconds Python is timecritical auto_create_subject_id = False goodbye_text = "Ending experiment..." goodbye_delay = 3000 fast_quit = False quit_key = _constants.K_ESCAPE pause_key = _constants.K_p open_gl = True window_mode = False window_size = (800, 600) event_logging = 1 # 1 = default, 2 = extensive, 0 or False = off stdout_logging = True audiosystem_autostart = True audiosystem_sample_rate = 44100 audiosystem_bit_depth = -16 # Negative values mean signed sample values audiosystem_channels = 2 audiosystem_buffer_size = 2048 _mode_settings = None python-expyriment-0.7.0+git34-g55a4e7e/expyriment/control/__init__.py0000644000175000017500000000117112314561273024776 0ustar oliveroliver""" The control package of expyriment. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from _miscellaneous import start_audiosystem, stop_audiosystem, \ get_audiosystem_is_playing, wait_end_audiosystem, \ set_develop_mode, get_defaults, register_wait_callback_function, \ unregister_wait_callback_function, is_idle_running, is_ipython_running from _experiment_control import initialize, start, pause, end from _test_suite import run_test_suite python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/0000775000175000017500000000000012314561273022675 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/0000775000175000017500000000000012314561273024203 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_lcdsymbol.py0000644000175000017500000003325512314561273026712 0ustar oliveroliver#!/usr/bin/env python """ A LCD symbol. This module contains a class implementing a LCD symbol. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import math import copy import pygame import defaults import expyriment from expyriment.stimuli._visual import Visual from expyriment.stimuli.extras._polygondot import PolygonDot class LcdSymbol(Visual): """A LCD symbol class. IDs for points and line :: Point= Lines = 0---1 X-0-X | | 1 2 2---3 X-3-X | | 4 5 4---5 X-6-X Valid shapes are: '0','1','2','3','4','5','6','7','8','9' 'A','C','E','F','U','H','L','P','h' """ _shapes = {"0":(0, 1, 2, 4, 5, 6), "1":(2, 5), "2":(0, 2, 3, 4, 6), "3":(0, 2, 3, 5, 6), "4":(1, 2, 3, 5), "5":(0, 1, 3, 5, 6), "6":(0, 1, 3, 4, 5, 6), "7":(0, 2 , 5), "8":(0, 1, 2 , 3 , 4 , 5 , 6), "9":(0, 1 , 2 , 3 , 5, 6), "A":(0, 1, 2, 3, 4, 5), "C":(0, 1, 4, 6), "E":(0, 1, 3, 4, 6), "F":(0, 1, 3, 4), "U":(1, 4, 6, 5, 2), "H":(1, 2, 3, 4, 5), "L":(1, 4, 6), "P":(0, 1, 2, 3, 4), "h":(1, 3, 4, 5) } _lines = ((0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)) def __init__(self, shape, position=None, size=None, colour=None, inactive_colour=None, background_colour=None, line_width=None, gap=None, simple_lines=None): """Create a LCD symbol. Parameters ---------- shape : list shape to show position : (int, int), optional position to show the symbol size : (int, int) size of the LCD symbol colour : (int, int, int), optional LCD symbol colour inactive_colour : (int, int, int), optional colour of inactive lines background_colour : (int, int, int), optional line_width : int, optional width of the lines gap :int, optional gap between lines simple_lines : bool, optional use simple lines """ Visual.__init__(self, position) if shape in self._shapes: self._shape = self._shapes[shape] else: self._shape = shape if size is not None: self._width = size[0] self._height = size[1] else: size = defaults.lcdsymbol_size if size is None: try: size = expyriment._active_exp.screen.surface.get_size() except: raise RuntimeError("Could not get size of screen!") self._width = size[0] self._height = size[1] if colour is None: colour = defaults.lcdsymbol_colour if colour is not None: self._colour = colour else: self._colour = expyriment._active_exp.foreground_colour if inactive_colour is not None: self._inactive_colour = inactive_colour else: self._inactive_colour = \ defaults.lcdsymbol_inactive_colour if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ defaults.lcdsymbol_background_colour if line_width is not None: self._line_width = line_width else: self._line_width = defaults.lcdsymbol_line_width if gap is not None: self._gap = gap else: self._gap = defaults.lcdsymbol_gap if simple_lines is not None: self._simple_lines = simple_lines else: self._simple_lines = defaults.lcdsymbol_simple_lines x = int(self.line_width / 2.0) + 1 self._points = (PolygonDot(radius=0, position=(x, x)), PolygonDot(radius=0, position=(self._width - x, x)), \ PolygonDot(radius=0, position=(x, math.floor(self._height / 2))), \ PolygonDot(radius=0, position=(self._width - x, math.floor(self._height / 2))), \ PolygonDot(radius=0, position=(x, self._height - x)), \ PolygonDot(radius=0, position=(self._width - x, self._height - x))) expyriment.stimuli._stimulus.Stimulus._id_counter -= 6 _getter_exception_message = "Cannot set {0} if surface exists!" @property def shape(self): """Getter for shape.""" return self._shape @shape.setter def shape(self, value): """Setter for shape.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format("shape")) else: if value in self.shapes: self._shape = self.shapes[value] else: self._shape = value @property def size(self): """Getter for size.""" return (self._width, self._height) @size.setter def size(self, value): """Setter for size.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format("shape")) else: self._width = value[0] self._height = value[1] self._points = (PolygonDot(radius=0, position=(0, 0)), PolygonDot(radius=0, position=(self._width, 0)), PolygonDot(radisu=0, position=(0, math.floor(self._height / 2))), PolygonDot(radius=0, position=(self._width, math.floor(self._height / 2))), PolygonDot(radius=0, position=(0, self._height)), PolygonDot(radius=0, position=(self._width, self._height))) expyriment.stimuli._stimulus.Stimulus._id_counter -= 6 @property def colour(self): """Getter for colour.""" return self._colour @colour.setter def colour(self, value): """Setter for colour.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format("colour")) else: self._colour = value @property def inactive_colour(self): """Getter for inactive_colour.""" return self._inactive_colour @inactive_colour.setter def inactive_colour(self, value): """Setter for inactive_colour.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format( "inactive_colour")) else: self._inactive_colour = value @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @background_colour.setter def background_colour(self, value): """Setter for background_colour.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format( "background_colour")) else: self._background_colour = value @property def line_width(self): """Getter for line_width.""" return self._line_width @line_width.setter def line_width(self, value): """Setter for line_width.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format("line_width")) else: self._line_width = value @property def gap(self): """Getter for gap.""" return self._gap @gap.setter def gap(self, value): """Setter for gap.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format("gap")) else: self._gap = value @property def simple_lines(self): """Getter for simple_lines.""" return self._simple_lines @simple_lines.setter def simple_lines(self, value): """Setter for simple_lines.""" if self.has_surface: raise AttributeError( LcdSymbol._getter_exception_message.format( "simple_lines")) else: self._simple_lines = value @property def points(self): """Getter for points.""" return self._points def _create_surface(self): """Create the surface of the stimulus.""" surface = pygame.surface.Surface((self._width, self._height), pygame.SRCALPHA).convert_alpha() if self.background_colour is not None: surface.fill(self.background_colour) if self.inactive_colour is not None: #draw background for x in self._shapes["8"]: moved_poly = [] for p in self.get_line_polygon(x): moved_poly.append((p[0], p[1])) pygame.draw.polygon(surface, self.inactive_colour, moved_poly, 0) if len(self._shape) > 0: for x in self._shape: moved_poly = [] for p in self.get_line_polygon(x): moved_poly.append((p[0], p[1])) pygame.draw.polygon(surface, self.colour, moved_poly, 0) return surface def get_line_points(self, idx): """Return point tuple including start and end point of a line. Parameters ---------- idx : int index of the line Returns ------- points : ((int, int), (int,int)) point tuple including start and end point of a line """ if idx == 0 or idx == 3 or idx == 6 : # horizontal line p1 = copy.copy(self.points[self._lines[idx][0]]) p2 = copy.copy(self.points[self._lines[idx][1]]) p1.position[0] = p1.position[0] + self.gap p2.position[0] = p2.position[0] - self.gap return (p1.position, p2.position) elif idx == 1 or idx == 2 or idx == 4 or idx == 5: # vertical line p1 = copy.copy(self.points[self._lines[idx][0]]) p2 = copy.copy(self.points[self._lines[idx][1]]) p1.position[1] = p1.position[1] + self.gap p2.position[1] = p2.position[1] - self.gap return (p1.position, p2.position) else: return () def get_line_polygon(self, idx): """Return point list describing the line as polygon. Parameters ---------- idx : int index of the line (int) Returns ------- point : list of tuple """ if idx == 0 or idx == 3 or idx == 6 : # horizontal line return self._line_to_polygon(self.points[self._lines[idx][0]], \ self.points[self._lines[idx][1]], True) elif idx == 1 or idx == 2 or idx == 4 or idx == 5: # vertical line return self._line_to_polygon(self.points[self._lines[idx][0]], \ self.points[self._lines[idx][1]], False) else: return () def _line_to_polygon(self, start, end, horizontal): """Convert a line defined by start and end points to a polygon. Parameters ---------- start : int start point end : int end point horizontal : bool True or False """ w2 = math.floor((self.line_width - 1) / 2) if w2 <= 0: w2 = 1 poly = [] poly.append(copy.copy(start.position)) p = PolygonDot(radius=0, position=(0, 0)) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 if horizontal: p.position[0] = start.position[0] + self.gap p.position[1] = start.position[1] - w2 poly.append(copy.copy(p.position)) p.position[0] = end.position[0] - self.gap poly.append(copy.copy(p.position)) poly.append(copy.copy(end.position)) p.position[0] = end.position[0] - self.gap p.position[1] = end.position[1] + w2 poly.append(copy.copy(p.position)) p.position[0] = start.position[0] + self.gap poly.append(copy.copy(p.position)) else: p.position[0] = start.position[0] + w2 p.position[1] = start.position[1] + self.gap poly.append(copy.copy(p.position)) p.position[1] = end.position[1] - self.gap poly.append(copy.copy(p.position)) poly.append(copy.copy(end.position)) p.position[0] = end.position[0] - w2 p.position[1] = end.position[1] - self.gap poly.append(copy.copy(p.position)) p.position[1] = start.position[1] + self.gap poly.append(copy.copy(p.position)) if self.simple_lines: poly.pop(3) poly.pop(0) return tuple(poly) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() lcdsymbol = LcdSymbol("A", size=(100, 100)) lcdsymbol.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_noisetone_defaults.py0000644000175000017500000000013312314561273030601 0ustar oliveroliver# NoiseTone noisetone_samplerate = 44100 noisetone_bitdepth = 16 noisetone_amplitude = 0.5 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_noisetone.py0000644000175000017500000001215112314561273026715 0ustar oliveroliver#!/usr/bin/env python """ The noise tone stimulus module. This module contains a class implementing a noise tone stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import wave import struct import itertools import tempfile import shutil import random import defaults from expyriment.stimuli import defaults as stim_defaults from expyriment.stimuli._audio import Audio class NoiseTone(Audio): """A class implementing a noise tone stimulus.""" def __init__(self, duration, samplerate=None, bitdepth=None, amplitude=None): """Create a noise tone. Parameters ---------- duration : int duration of the noise tone in ms frequency : int, optional frequency of the noise tone samplerate : int, optional samplerate of the noise tone bitdepth : int, optional bitdeth of the noise tone amplitude : int, optional amplitude of the noise tone """ self._duration = duration / 1000.0 if samplerate is None: samplerate = defaults.noisetone_samplerate self._samplerate = samplerate if bitdepth is None: bitdepth = defaults.noisetone_bitdepth self._bitdepth = bitdepth if amplitude is None: amplitude = defaults.noisetone_amplitude self._amplitude = amplitude filename = self._create_noise_wave() Audio.__init__(self, filename) @property def duration(self): """Getter for duration.""" return self._duration * 1000.0 @duration.setter def duration(self, value): """Setter for duration.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "duration")) else: self._duration = value / 1000.0 self._filename = self._create_sine_wave() @property def samplerate(self): """Getter for samplerate.""" return self._samplerate @samplerate.setter def samplerate(self, value): """Setter for samplerate.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "samplerate")) else: self._samplerate = value self._filename = self._create_sine_wave() @property def bitdepth(self): """Getter for bitdepth.""" return self._bitdepth @bitdepth.setter def bitdepth(self, value): """Setter for bitdepth.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "bitdepth")) else: self._bitdepth = value self._filename = self._create_sine_wave() @property def amplitude(self): """Getter for amplitude.""" return self._amplitude @amplitude.setter def amplitude(self, value): """Setter for amplitude.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "amplitude")) else: self._amplitude = value self._filename = self._create_sine_wave() def _grouper(self, n, iterable, fillvalue=None): """Write in chunks.""" args = [iter(iterable)] * n return itertools.izip_longest(fillvalue=fillvalue, *args) def _create_noise_wave(self): """Create the sine wave.""" random.seed() noise = (float(self._amplitude) * random.uniform(-1, 1) for _ in \ itertools.count(0)) channels = ((noise,),) n_samples = self._duration * self._samplerate samples = itertools.islice(itertools.izip( *(itertools.imap(sum, itertools.izip(*channel)) \ for channel in channels)), n_samples) fid, filename = tempfile.mkstemp(dir=stim_defaults.tempdir, suffix=".wav") os.close(fid) w = wave.open(filename, 'w') w.setparams((1, self._bitdepth / 8, self._samplerate, n_samples, 'NONE', 'not compressed')) max_amplitude = float(int((2 ** (self._bitdepth)) / 2) - 1) for chunk in self._grouper(2048, samples): frames = ''.join(''.join(struct.pack( 'h', int(max_amplitude * sample)) for sample in channels) \ for channels in chunk if channels is not None) w.writeframesraw(frames) w.close() return filename def save(self, filename): """Save the sine tone to a file. Parameters ---------- filename : str filename the sine tone should be saved to """ shutil.copy(self._filename, filename) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() control.start_audiosystem() sine = NoiseTone(duration=1000) sine.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_dotcloud.py0000644000175000017500000001676712314561273026550 0ustar oliveroliver#!/usr/bin/env python """ A dotcloud stimulus. This module contains a class implementing a dotcloud stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import random import pygame import expyriment from expyriment.stimuli._visual import Visual from expyriment.stimuli._dot import Dot as Dot import defaults class DotCloud(Visual): """A dot cloud class. This class creates dots arranged in a circular cloud. """ def __init__(self, radius=None, position=None, background_colour=None, dot_colour=None): """Create a dot cloud. Parameters ---------- radius : int, optional radius of the cloud position : (int, int), optional position of the stimulus background_colour : (int, int, int), optional colour of the background dot_colour : (int, int, int), optional colour of the dots """ Visual.__init__(self, position) self._cloud = [] if radius is not None: self._radius = radius else: radius = defaults.dotcloud_radius if radius is None: try: self._radius = min( expyriment._active_exp.screen.surface.get_size()) / 2 except: raise RuntimeError("Could not get size of screen!") if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ defaults.dotcloud_background_colour if dot_colour is None: dot_colour = defaults.dotcloud_dot_colour if dot_colour is not None: self._dot_colour = dot_colour else: self._dot_colour = expyriment._active_exp.foreground_colour self.create_area() _getter_exception_message = "Cannot set {0} if surface exists!" @property def radius(self): """Getter for radius.""" return self._radius @radius.setter def radius(self, value): """Setter for radius.""" if self.has_surface: raise AttributeError(DotCloud._getter_exception_message.format( "radius")) else: self._radius = value self.create_area() @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @background_colour.setter def background_colour(self, value): """Setter for background_colour.""" if self.has_surface: raise AttributeError(DotCloud._getter_exception_message.format( "background_colour")) else: self._background_colour = value self.create_area() @property def dot_colour(self): """Getter for dot_colour.""" return self._dot_colour @dot_colour.setter def dot_colour(self, value): """Setter for dot_colour.""" if self.has_surface: raise AttributeError(DotCloud._getter_exception_message.format( "dot_colour")) else: self._dot_colour = value self.create_area() @property def area(self): """Getter for area.""" return self._area def _create_surface(self): """Create the surface of the stimulus.""" surface = self.area._get_surface() for dot in self._cloud: dot.rect = pygame.Rect((0, 0), dot.surface_size) surface_size = surface.get_size() dot.rect.center = [dot.position[0] + surface_size[0] / 2, dot.position[1] + surface_size[1] / 2] surface.blit(dot._get_surface(), dot.rect) return surface def create_area(self): """Create the area of the cloud (a dot object itself).""" self._area = Dot(radius=self._radius, position=(0, 0), colour=self._background_colour) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._area._set_surface(pygame.surface.Surface( (self.radius * 2, self.radius * 2), pygame.SRCALPHA).convert_alpha()) if self._background_colour is not None: pygame.draw.circle(self._area._get_surface(), self._background_colour, (self._radius, self._radius), self._radius) def _is_overlapping_with_point(self, dot, gap): """Return True if a dot in the cloud is overlapping with another dot. Parameters ---------- dot : stimuli.dot the other dot gap : int constant added to the distance Returns ------- out : bool True if a dot in the cloud is overlapping with another dot """ for elem in self._cloud: d = elem.distance(dot) if d <= (elem.radius + dot.radius + gap): return True return False def make(self, n_dots, dot_radius, gap=0): """Make the cloud by randomly putting dots on it. Parameters ---------- n_dots : int number of dots to put into the cloud dot_radius : int radius of the dots gap : int, optional gap between dots (default = 0) """ top_left = dot_radius - self._radius bottom_right = self._radius - dot_radius remix = 0 while(True): #remix-loop self._cloud = [] remix = remix + 1 reps = 0 while(True): #find a solution dot = Dot(radius=dot_radius) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 dot.position = (random.randint(top_left, bottom_right), random.randint(top_left, bottom_right)) reps = reps + 1 if dot.is_inside(self.area): if not self._is_overlapping_with_point(dot, gap): self._cloud.append(dot) reps = 0 if reps > 10000: break if len(self._cloud) >= n_dots: self.clear_surface() return True if remix > 10: message = "Dotcloud make: Cannot find a solution." print("Warning: ", message) if self._logging: expyriment._active_exp._event_file_log(message) return False def shuffel_dot_sequence(self, from_idx=0, to_idx= -1): """Shuffle the dots sequence. Parameters ---------- from_idx : int, optional index to start from (default = 0) to_idx : int, optional index to end on (default = -1) """ if (from_idx < 0): from_idx = 0 if to_idx < from_idx or to_idx >= len(self._cloud): to_idx = len(self._cloud) - 1 for x in range(from_idx, to_idx) : r = random.randint(from_idx, to_idx) self._cloud[r], self._cloud[x] = self._cloud[x], self._cloud[r] if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() dotcloud = DotCloud() dotcloud.make(25, 10) dotcloud.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygondot_defaults.py0000644000175000017500000000024512314561273031000 0ustar oliveroliver# PolygonDot polygondot_colour = None # 'None' is experiment_text_colour polygondot_position = (0, 0) polygondot_anti_aliasing = 10 polygondot_resolution_factor = 1 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_randomdotkinematogram.py0000644000175000017500000003510012314561273031277 0ustar oliveroliver#!/usr/bin/env python """ A random dot kinematogram (stimulus-like). This module contains a class implementing a random dot kinematogram. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import math import random import defaults from expyriment.io import Keyboard from expyriment.stimuli import Canvas, Circle from expyriment.stimuli._stimulus import Stimulus from expyriment.misc import Clock class RandomDotKinematogram(Stimulus): """Random Dot Kinematogram""" def __init__(self, area_radius, n_dots, target_direction, target_dot_ratio, position = None, dot_speed = None, dot_lifetime = None, dot_diameter = None, dot_colour=None, background_colour = None, north_up_clockwise = None): """Create a Random Dot Kinematogram Parameters: ----------- area_radius : int the radius of the stimulus area n_dots : int number of moving dots target_direction : int, float (0-360) movement target direction in degrees target_dot_ratio : float (0-1) ratio of dots that move consistently in the same target direction (the rest of the target moves in a random direction) can be sometimes only approximated! self.target_dot_ratio returns the precise actual target dot ratio position : (int, int), optional position of the stimulus dot_speed : int, optional the moving speed in pixel per second (default=100) dot_lifetime : int, optional the time the object lives in milliseconds (default 400) dot_diameter : int, optional diameter of the dots (default = 5) dot_colour : (int, int, int), optional colour (RGB) of the dots (default=experiment.foreground_colour) background_colour : (int, int, int), optional colour (RGB) of the background (default=experiment.background_colour) north_up_clockwise : bool, optional if true (default) all directional information refer to an north up and clockwise system otherwise 0 is right, counterclockwise (default=True) Notes: ------ Logging is switch off per default """ if dot_speed is None: dot_speed = defaults.randomdotkinematogram_dot_speed if dot_lifetime is None: dot_lifetime = defaults.randomdotkinematogram_dot_lifetime if dot_diameter is None: dot_diameter = defaults.randomdotkinematogram_dot_diameter if dot_colour is None: dot_colour = defaults.randomdotkinematogram_dot_colour if north_up_clockwise is None: north_up_clockwise = defaults.randomdotkinematogram_north_up_clockwise if position is None: position = defaults.randomdotkinematogram_position if background_colour is None: background_colour = defaults.randomdotkinematogram_background_colour self.area_radius = area_radius self.dot_speed = dot_speed self.dot_lifetime = dot_lifetime self.dot_diameter = dot_diameter self.dot_colour = dot_colour self.north_up_clockwise = north_up_clockwise self._canvas = Canvas(size=(2*(self.area_radius + self.dot_diameter), 2*(self.area_radius + self.dot_diameter)), position=position, colour=background_colour) self.reset(n_dots=n_dots, target_direction=target_direction, target_dot_ratio=target_dot_ratio) self.set_logging(False) def reset(self, n_dots, target_direction, target_dot_ratio): """Reset and recreate dot pattern Parameters: ----------- n_dots : int number of moving dots target_direction : int, float (0-360) movement target direction in degrees target_dot_ratio : float (0-1) ratio of dots that move consistently in the same target direction (the rest of the target moves in a random direction) can be sometimes only approximated! self.target_dot_ratio returns the precise actual target dot ratio """ self.target_direction = target_direction self.dots = map(lambda x : self._make_random_dot(direction=None, extra_age=int(random.random() * self.dot_lifetime)), range(n_dots)) self.target_dot_ratio = target_dot_ratio @property def n_dots(self): """Getter for n_dots.""" return len(self.dots) @property def logging(self): """Getter for logging.""" return self._canvas.logging def set_logging(self, onoff): """Set logging of this object on or off Parameters: ----------- onoff : bool set logging on (True) or off (False) """ self._canvas.set_logging(onoff) @property def n_target_dots(self): return sum(map(lambda x: int(x.is_target), self.dots)) @property def target_dot_ratio(self): """Getter for target dot ratio""" return self.n_target_dots / float(len(self.dots)) @target_dot_ratio.setter def target_dot_ratio(self, value): if value<0: value = 0 if value > 1: value = 1 curr_n_targets = self.n_target_dots goal_n_targets = int(len(self.dots) * value) while goal_n_targets != curr_n_targets: if goal_n_targets > curr_n_targets: # remove non target and add target for d in self.dots: if not d.is_target: self.dots.remove(d) break d = self._make_random_dot(direction=self.target_direction, extra_age=int(random.random() * self.dot_lifetime)) d.is_target = True self.dots.append(d) elif goal_n_targets < curr_n_targets: # remove a target and add non target for d in self.dots: if d.is_target: self.dots.remove(d) break self.dots.append(self._make_random_dot(direction=None, extra_age=int(random.random() * self.dot_lifetime))) curr_n_targets = self.n_target_dots @property def last_stimulus(self): """Getter for the last plotted stimulus""" return self._canvas def _make_random_dot(self, direction=None, extra_age=0): while (True): pos = (int(self.area_radius - random.random()*2*self.area_radius), int(self.area_radius - random.random()*2*self.area_radius)) if math.hypot(pos[0],pos[1])< self.area_radius: break if direction is None: direction = random.random() * 360 return MovingPosition(position=pos, direction=direction, speed = self.dot_speed, extra_age=extra_age, lifetime = self.dot_lifetime, north_up_clockwise=self.north_up_clockwise) def make_frame(self, background_stimulus=None): """Make new frame. The function creates the current random dot kinematogram and returns it as Expyriment stimulus. Parameters: ----------- background_stimulus : Expyriment stimulus, optional optional stimulus to be plotted in the background (default=None) Notes: ------ Just loop this function and plot the returned stimulus. See present_and_wait_keyboard Returns: -------- stimulus : return the current as Expyriment stimulus """ self._canvas.clear_surface() if background_stimulus is not None: background_stimulus.plot(self._canvas) def _process_dot(d): if d.is_dead or d.is_outside(self.area_radius): if d.is_target: d = self._make_random_dot(direction=d.direction) d.is_target = True else: d = self._make_random_dot() Circle(position = d.position, diameter=self.dot_diameter, colour=self.dot_colour).plot(self._canvas) return d self.dots = map(_process_dot, self.dots) return self._canvas def present_and_wait_keyboard(self, background_stimulus=None, check_keys = None, change_parameter=(None, None), duration=None, button_box=None): """Present the random dot kinematogram and wait for keyboard press. Parameters: ----------- background_stimulus : Expyriment stimulus, optional optional stimulus to be plotted in the background (default=None) check_keys : int or list, optional a specific key or list of keys to check change_parameter : tuple (int, int), optional, default = (None, None) [step size (target dot ratio), step interval (ms)] if both parameter are defined (not None), target dot ratio changes accordingly while presentation duration: int, optional maximum duration to wait for keypress button_box: expyriment io.ButtonBox object, optional if not the keyboard but a button_box should be used (e.g. io.StreamingButtonBox) """ from expyriment import _active_exp RT = Clock() if button_box is None: button_box = _active_exp.keyboard button_box.clear() last_change_time = RT.stopwatch_time while(True): if None not in change_parameter: if RT.stopwatch_time >= change_parameter[1] + last_change_time: last_change_time = RT.stopwatch_time self.target_dot_ratio = self.target_dot_ratio + \ change_parameter[0] self.make_frame(background_stimulus=background_stimulus).present() if isinstance(button_box, Keyboard): key = button_box.check(keys=check_keys) else: _active_exp.keyboard.process_control_keys() key = button_box.check(codes=check_keys) if key is not None: break if duration is not None and RT.stopwatch_time >= duration: return (None, None) return key, RT.stopwatch_time class MovingPosition(object): def __init__(self, position, direction, speed, lifetime, extra_age=0, north_up_clockwise = True, is_target=False): """Create a MovingPosition Parameters ---------- position : (int, int) start position direction : int, float (0-360) movement direction in degrees speed : int the moving speed in pixel per second lifetime : int the time the object lives in milliseconds extra_age : int, optional the object can have already an age when creating (default=0) north_up_clockwise : bool, optional if true (default) all directional information refer to an north up and clockwise system otherwise 0 is right, counterclockwise (default=True) is_target : bool target position """ self._start_position = list(position) self.lifetime = lifetime self.extra_age = extra_age # add extra age for shorter lifetime self.is_target = is_target self._speed = speed self._north_up_clockwise = north_up_clockwise, self._direction = direction self._update_movement_vector() self._clock = Clock() @property def is_dead(self): """Return True is lifetime of the object is over""" return (self._clock.time + self.extra_age >= self.lifetime) def is_outside(self, the_range): """Return True the object is outside the range from (0,0)""" pos = self.position return (math.hypot(pos[0], pos[1])>=the_range) @property def north_up_clockwise(self): """getter for north up and clockwise""" return self._north_up_clockwise @property def position(self): """The current position (depends on time). Note: This property changes continuously over time, since the position is moving. """ return (self._start_position[0] + self._clock.time * self._movement_vector[0], self._start_position[1] + self._clock.time * self._movement_vector[1]) @property def direction(self): """Getter for direction.""" return self._direction @property def speed(self): """Getter for speed.""" return self._speed @direction.setter def direction(self, x): self._direction = x self._update_movement_vector() @speed.setter def speed(self, x): """speed in pix per second""" self._speed = x self._update_movement_vector() def _update_movement_vector(self): if self._north_up_clockwise: direction = 450 - self._direction else: direction = self._direction angle = direction * (math.pi)/180 speed = self._speed/float(1000) self._movement_vector = (speed * math.cos(angle), speed * math.sin(angle)) if __name__ == "__main__": from expyriment import control, design control.set_develop_mode(True) exp = control.initialize() direction = design.randomize.rand_int(0, 360) # first rdk stimulus with 20% consitencyin random direction rdk = RandomDotKinematogram(area_radius=200, n_dots=150, target_direction = direction, target_dot_ratio = 0.20, dot_speed = 80, dot_lifetime = 600, dot_diameter=5, dot_colour=None) key, rt = rdk.present_and_wait_keyboard() # smae direct buy with 80% consitency rdk = RandomDotKinematogram(area_radius=200, n_dots=150, target_direction = direction, target_dot_ratio = 0.80, dot_speed = 80, dot_lifetime = 600, dot_diameter=5, dot_colour=None) key, rt = rdk.present_and_wait_keyboard() print direction python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygonrectangle_defaults.py0000644000175000017500000000021012314561273032146 0ustar oliveroliverpolygonrectangle_colour = None # 'None' is experiment_text_colour polygonrectangle_position = (0, 0) polygonrectangle_anti_aliasing = 0 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_visualmask.py0000644000175000017500000001155112314561273027074 0ustar oliveroliver#!/usr/bin/env python """ A Visual Mask. This module contains a class implementing a Visual Mask. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from random import shuffle import tempfile import os try: from PIL import Image, ImageDraw, ImageFilter #import PIL except: Image = None import expyriment from expyriment.misc._timer import get_time from expyriment.stimuli._picture import Picture import defaults class VisualMask(Picture): """A class implementing a visual mask stimulus.""" def __init__(self, size, position=None, dot_size=None, background_colour=None, dot_colour=None, dot_percentage=None, smoothing=None): """Create a visual mask. Parameters ---------- size : (int, int) size (x, y) of the mask position : (int, int), optional position of the mask stimulus dot_size : (int, int), optional size (x, y) of the dots background_colour : (int, int), optional dot_colour : (int, int), optional dot_percentage : int, optional percentage of covered area by the dots (1 to 100) smoothing : int, optional smoothing (default=3) """ import types if type(Image) is not types.ModuleType: message = """VisualMask can not be initialized. The Python package 'Python Imaging Library (PIL)' is not installed.""" raise ImportError(message) fid, filename = tempfile.mkstemp( dir=expyriment.stimuli.defaults.tempdir, suffix=".png") os.close(fid) Picture.__init__(self, filename, position) self._size = size if dot_size is not None: self.dot_size = dot_size else: self.dot_size = defaults.visualmask_dot_size if background_colour is None: self.background_colour = defaults.visualmask_background_colour if background_colour is not None: self.background_colour = background_colour else: self.background_colour = expyriment._active_exp.background_colour if dot_colour is None: self.dot_colour = defaults.visualmask_dot_colour if dot_colour is not None: self.dot_colour = dot_colour else: self.dot_colour = expyriment._active_exp.foreground_colour if dot_percentage is not None: self.dot_percentage = dot_percentage else: self.dot_percentage = defaults.visualmask_dot_percentage if smoothing is not None: self.smoothing = smoothing else: self.smoothing = defaults.visualmask_smoothing self.create_mask() def create_mask(self): """Creates a new visual mask. Notes ----- CAUTION: Depending on the size of the stimulus, this method may take some time to execute. Returns ------- time : int the time it took to execute this method in ms """ start = get_time() was_preloaded = self.is_preloaded if was_preloaded: self.unload() s = (self._size[0] + 4 * self.smoothing, self._size[1] + 4 * self.smoothing) #somewhat larger mask im = Image.new("RGB", s) draw = ImageDraw.Draw(im) draw.rectangle([(0, 0), s], outline=self.background_colour, fill=self.background_colour) n_dots_x = int(s[0] / self.dot_size[0]) + 1 n_dots_y = int(s[1] / self.dot_size[1]) + 1 dots = range(n_dots_x * n_dots_y) shuffle(dots) for d in dots[:int(len(dots) * self.dot_percentage / 100)]: y = (d / n_dots_x) * self.dot_size[1] x = (d % n_dots_x) * self.dot_size[0] draw.rectangle([(x, y), (x + self.dot_size[0], y + self.dot_size[1])], outline=self.dot_colour, fill=self.dot_colour) for x in range(self.smoothing): im = im.filter(ImageFilter.BLUR).filter(ImageFilter.SMOOTH_MORE) #crop image and save c = (im.size[0] / 2, im.size[1] / 2) box = (c[0] - self._size[0] / 2, c[1] - self._size[1] / 2, c[0] + self._size[0] / 2, c[1] + self._size[1] / 2) im = im.crop(box) im.save(self._filename, format="png") if was_preloaded: self.preload() return int((get_time() - start) * 1000) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() mask = VisualMask(size=(200, 200)) mask.present() print mask.surface_size exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_stimuluscloud.py0000644000175000017500000001532212314561273027631 0ustar oliveroliver#!/usr/bin/env python """ A stimulus cloud stimulus. This module contains a class implementing a stimulus cloud stimulus. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import random import pygame import defaults import expyriment from expyriment.stimuli._visual import Visual class StimulusCloud(Visual): """A stimulus cloud class. This class produces a cloud of ANY visual stimuli. The cloud will be of rectengular shape! """ def __init__(self, size=None, position=None, background_colour=None): """Create a stimulus cloud. Parameters ---------- size : (int, int), optional size of the cloud position : (int, int), optional position of the cloud background_colour : (int, int, int), optional colour of the background """ Visual.__init__(self, position) self._cloud = [] if size is not None: self._size = size else: size = defaults.stimuluscloud_size if size is None: try: self._size = expyriment._active_exp.screen.surface.get_size() except: raise RuntimeError("Could not get size of screen!") if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ defaults.stimuluscloud_background_colour self._rect = None _getter_exception_message = "Cannot set {0} if surface exists!" @property def size(self): """Getter for size.""" return self._size @size.setter def size(self, value): """Setter for size.""" if self.has_surface: raise AttributeError( StimulusCloud._getter_exception_message.format("size")) else: self._size = value @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @background_colour.setter def background_colour(self, value): """Setter for background_colour.""" if self.has_surface: raise AttributeError( StimulusCloud._getter_exception_message.format("background_colour")) else: self._background_colour = value def _create_surface(self): """Create the surface of the stimulus.""" surface = pygame.surface.Surface(self.size, pygame.SRCALPHA).convert_alpha() if self.background_colour is not None: surface.fill(self.background_colour) for stim in self._cloud: stim.rect = pygame.Rect((0, 0), stim.surface_size) surface_size = surface.get_size() stim.rect.center = [stim.position[0] + surface_size[0] / 2, - stim.position[1] + surface_size[1] / 2] surface.blit(stim._get_surface(), stim.rect) return surface def make(self, stimuli, min_distance=None): """Make the cloud by randomly putting stimuli on it. Notes ----- If min_distance is None, the stimuli will automatically spaced to not overlap. This will result in a long computation! Set the distance manually to space them wit a minimal distance between centers. This will result in a way shorter computation! This will build surfaces for all stimuli in the cloud! Parameters ---------- stimuli : list list of stimuli to put in the cloud min_distance : int, optional minimal allowed distance between stimuli """ surface = pygame.surface.Surface(self.size, pygame.SRCALPHA).convert_alpha() surface.fill((0, 0, 0)) self._set_surface(surface) remix = 0 while(True): #remix-loop self._cloud = [] remix = remix + 1 for stimulus in stimuli: reps = 0 stimulus._set_surface(stimulus._get_surface()) while(True): #find a solution stimulus.position = (random.randint(-self.size[0] / 2, self.size[0] / 2), random.randint(-self.size[1] / 2, self.size[1] / 2)) reps = reps + 1 if stimulus.inside_stimulus(self): okay = True if min_distance is None: for s in self._cloud: if stimulus.overlapping_with_stimulus(s)[0]: okay = False break else: for s in self._cloud: if stimulus.distance(s) < min_distance: okay = False break if okay: self._cloud.append(stimulus) reps = 0 if len(self._cloud) == len(stimuli): self.clear_surface() return True break if reps > 10000: break if (remix > 10): message = "Stimuluscloud make: Cannot find a solution." print("Warning: ", message) return False def shuffel_surface_sequence(self, from_idx=0, to_idx= -1): """Shuffle the surfaces sequence. Parameters ---------- from_idx : int, optional index to start from (default = 0) to_idx : int, optional index to end on (default = -1) """ if (from_idx < 0): from_idx = 0 if to_idx < from_idx or to_idx >= len(self._cloud): to_idx = len(self._cloud) - 1 for x in range(from_idx, to_idx) : r = random.randint(from_idx, to_idx) self._cloud[r], self._cloud[x] = self._cloud[x], self._cloud[r] if __name__ == "__main__": from expyriment.stimuli._textline import TextLine from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() stimuluscloud = StimulusCloud() stims = [] for i in range(0, 25): stims.append(TextLine("A")) stimuluscloud.make(stims) stimuluscloud.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygonellipse.py0000644000175000017500000000713712314561273027767 0ustar oliveroliver#!/usr/bin/env python """ An ellipse stimulus. This module contains a class implementing an ellipse stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import math as _math import defaults from expyriment.stimuli._shape import Shape from expyriment.misc import geometry as _geometry class PolygonEllipse(Shape): """A class implementing an ellipse stimulus.""" _default_number_of_vertices = 36 def __init__(self, size, position=None, line_width=None, colour=None, resolution_factor=None, anti_aliasing=None): """Create an ellipse. Parameters ---------- size : (int,int) size of the ellipse (x,y) position : (int, int, int), optional position of the stimulus colour : (int, int, int), optional line_width : int, optional if line width is 0, the shape is filled resolution_factor : int, optional The resolution_factor increases the resolution of the eclipse. The default factor is 1 resulting in 36 points describing the ellipse anti_aliasing : int, optional anti aliasing parameter """ if position is None: position = defaults.polygonellipse_position if colour is None: colour = defaults.polygonellipse_colour if anti_aliasing is None: anti_aliasing = defaults.polygonellipse_anti_aliasing if resolution_factor is None: resolution_factor = defaults.polygonellipse_resolution_factor if line_width is None: line_width = defaults.polygonellipse_line_width Shape.__init__(self, position=position, colour=colour, anti_aliasing=anti_aliasing, line_width=line_width) self._resolution_factor = resolution_factor self._ellipse_size = list(size) self._circumference = None n_vtx = self._default_number_of_vertices * self._resolution_factor s = 2 * _math.pi / n_vtx w, h = self.ellipse_size l = 0 points = [] while l < 2 * _math.pi: points.append([.5 * _math.cos(l) * w + .5 * w, .5 * _math.sin(l) * h + .5 * h]) l = l + s self._vertices = _geometry.points_to_vertices(points) self._update_points() @property def circumference(self): """Getter for circumference. Notes ----- Calculates the circumference if required. The algorithm for this calculation is taken from http://paulbourke.net/geometry/ellipsecirc/ Ramanujan, Second Approximation """ if self._circumference is None: a, b = self._ellipse_size h3 = 3 * (_math.pow((a - b), 2) / _math.pow((a + b), 2)) self._circumference = _math.pi * (a + b) * \ (1.0 + h3 / (10.0 + _math.sqrt(4.0 - h3))) return self._circumference @property def ellipse_size(self): """Getter for frame_size.""" return self._ellipse_size @property def resolution_factor(self): """Getter for the resolution.""" return self._resolution_factor if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() stim = PolygonEllipse(size=(100, 100), line_width=5) stim.present() exp.clock.wait(2000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_randomdotkinematogram_defaults.py0000644000175000017500000000051712314561273033172 0ustar oliveroliverrandomdotkinematogram_dot_speed = 100 randomdotkinematogram_dot_lifetime = 400 randomdotkinematogram_dot_diameter = 5 randomdotkinematogram_dot_colour = None # expyriment default randomdotkinematogram_background_colour = None # expyriment default randomdotkinematogram_north_up_clockwise = True randomdotkinematogram_position = (0,0) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygonline_defaults.py0000644000175000017500000000013312314561273031135 0ustar oliveroliverpolygonline_colour = None # 'None' is experiment_text_colour polygonline_anti_aliasing = 0 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygondot.py0000644000175000017500000000541112314561273027111 0ustar oliveroliver#!/usr/bin/env python """ A dot stimulus. This module contains a class implementing a dot stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from expyriment.stimuli.extras._polygonellipse import PolygonEllipse class PolygonDot(PolygonEllipse): """A class implementing a dot as a child of PolygonEllipse.""" def __init__(self, radius, position=None, colour=None, resolution_factor=None, anti_aliasing=None): """Create a dot. Parameters ---------- radius : int radius of the dot position : int, optional position of the stimulus colour : (int, int, int), optional colour of the dot resolution_factor : int, optional The resolution_factor increases the resolution of the eclipse. The default factor is 1 resulting in 36 points describing the ellipse. anti_aliasing : int, optional anti aliasing parameter """ if position is None: position = defaults.polygondot_position if colour is None: colour = defaults.polygondot_colour if anti_aliasing is None: anti_aliasing = defaults.polygondot_anti_aliasing if resolution_factor is None: resolution_factor = defaults.polygondot_resolution_factor if radius == 0: radius = 0.5 PolygonEllipse.__init__(self, size=[2 * radius, 2 * radius], position=position, resolution_factor=resolution_factor, colour=colour, line_width=0) @property def radius(self): """Getter for radius.""" return self.ellipse_size[0] / 2 def is_center_inside(self, other): """Return True if the center is inside another dot. Parameters ---------- other : stimuli.PolygonDot the other dot Returns ------- out : bool """ d = self.distance(other) return (d <= other.radius) def is_inside(self, other): """Return True if the whole dot is inside another dot. Parameters ---------- other : stimuli.PolygonDot other dot Returns ------- out : bool """ d = self.distance(other) return (d <= other.radius - self.radius) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() dot = PolygonDot(radius=100) dot.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_lcdsymbol_defaults.py0000644000175000017500000000065612314561273030600 0ustar oliveroliver# LcdSymbol lcdsymbol_size = None # 'None' is maximal size to fit on screen lcdsymbol_colour = None # 'None' is experiment_text_colour lcdsymbol_background_colour = None # 'None' is transparent lcdsymbol_inactive_colour = None # 'None' is transparent lcdsymbol_line_width = 1 lcdsymbol_gap = 5 lcdsymbol_simple_lines = False lcdsymbol_position = (0, 0) lcdsymbol_rotation = None lcdsymbol_scaling = None lcdsymbol_flipping = None python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_visualmask_defaults.py0000644000175000017500000000025412314561273030761 0ustar oliveroliver# VisualMask defaults visualmask_dot_size = (5, 5) visualmask_background_colour = None visualmask_dot_colour = None visualmask_dot_percentage = 50 visualmask_smoothing = 3 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygonrectangle.py0000644000175000017500000000343012314561273030266 0ustar oliveroliver#!/usr/bin/env python """ A Rectangle stimulus. This module contains a class implementing a rectangle stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from expyriment.stimuli._shape import Shape import expyriment class PolygonRectangle(Shape): """A class implementing a rectangle stimulus.""" def __init__(self, size, position=None, colour=None, anti_aliasing=None): """Create a filled rectangle. Parameters ---------- size : (int, int) size (width, height) of the Rectangle position : (int, int), optional position of the stimulus colour : (int, int, int), optional colour of the rectangle anti_aliasing : int, optional anti aliasing parameter """ if position is None: position = defaults.polygonrectangle_position if colour is None: colour = defaults.polygonrectangle_colour if colour is None: colour = expyriment._active_exp.foreground_colour if anti_aliasing is None: anti_aliasing = defaults.polygonrectangle_anti_aliasing Shape.__init__(self, position=position, colour=colour, line_width=0, anti_aliasing=anti_aliasing) self.add_vertex((size[0], 0)) self.add_vertex((0, size[1])) self.add_vertex((-size[0], 0)) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() cnvs = Rectangle((20, 200), colour=(255, 0, 255)) cnvs.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_stimuluscloud_defaults.py0000644000175000017500000000041412314561273031514 0ustar oliveroliver# StimulusCloud stimuluscloud_size = None # 'None' is maximal size to fit on screen stimuluscloud_background_colour = None # 'None' is transparent stimuluscloud_position = (0, 0) stimuluscloud_rotation = None stimuluscloud_scaling = None stimuluscloud_flipping = None python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_stimuluscircle.py0000644000175000017500000001345212314561273027766 0ustar oliveroliver#!/usr/bin/env python """ A stimulus circle stimulus. This module contains a class implementing a stimulus circle stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import random import math import pygame import defaults from expyriment.stimuli._visual import Visual class StimulusCircle(Visual): """A stimulus circle class. """ def __init__(self, radius, stimuli, position=None, background_colour=None): """Create a stimulus circle. Parameters ---------- radius : int radius of the circle stimuli : expyriment stimulus stimuli to put into the circle position : (int, int), optional position of the circle background_colour : (int, int, int), optional background colour of the circle """ Visual.__init__(self, position) self._radius = radius self._stimuli = stimuli if background_colour is None: background_colour = \ defaults.stimuluscircle_background_colour self._background_colour = background_colour _getter_exception_message = "Cannot set {0} if surface exists!" @property def radius(self): """Getter for radius.""" return self._radius @radius.setter def radius(self, value): """Setter for radius.""" if self.has_surface: raise AttributeError( StimulusCircle._getter_exception_message.format("radius")) else: self._radius = value @property def stimuli(self): """Getter for stimuli.""" return self._stimuli @stimuli.setter def stimuli(self, value): """Setter for stimuli.""" if self.has_surface: raise AttributeError( StimulusCircle._getter_exception_message.format("stmuli")) else: self._stimuli = value @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @background_colour.setter def background_colour(self, value): """Setter for background_colour.""" if self.has_surface: raise AttributeError( StimulusCircle._getter_exception_message.format( "background_colour")) else: self._background_colour = value def _create_surface(self): """Create the surface of the stimulus.""" # Find largest stim max_x = 0 max_y = 0 for stim in self.stimuli: size = stim.surface_size if size[0] > max_x: max_x = size[0] if size[1] > max_y: max_y = size[1] # Build surface surface = pygame.surface.Surface( (self._radius * 2 + max_x, self._radius * 2 + max_y), pygame.SRCALPHA).convert_alpha() if self._background_colour is not None: surface.fill(self._background_colour) surface_size = surface.get_size() for stim in self._stimuli: stim_rect = pygame.Rect((0, 0), stim.surface_size) stim_rect.center = [stim.position[0] + surface_size[0] / 2, - stim.position[1] + surface_size[1] / 2] surface.blit(stim._get_surface(), stim_rect) return surface def _deg_to_polar(self, angle_in_degrees): """Converts degrees into polar coodrinates. Parameters ---------- angle_in_degrees : int angle in degrees Returns ------- angle : int angle in polar coordinates """ a = angle_in_degrees / 180.0 * math.pi return (math.cos(a) , math.sin(a)) def _get_circle_position(self, center, radius, angle): """Returns the position in the circle. Parameters ---------- center : (int, int) center of the circle radius : (int, int) radius of the circle angle : int angle to compute Returns ------- pos : int """ angle = angle - 90 p = self._deg_to_polar(angle) return (int(center[0] + p[0] * radius), int(center[1] - p[1] * radius)) def make(self, shuffle=True, jitter=True): """Make the circle. Parameters ---------- shuffle : bool, optional if True, positions will be shuffled (default = True) jitter : bool, optional if True, positions will be rotated between 0 and 1 step (default = True) """ random.seed() step = 360 / float(len(self._stimuli)) offset = 0 if jitter: offset = random.randint(0, int(step)) pos_list = [c for c, _s in enumerate(self._stimuli)] if shuffle: random.shuffle(pos_list) size = self.surface_size center = (size[0] / 2, size[1] / 2) for i, elem in enumerate(pos_list): d = offset + i * step xy = self._get_circle_position(center, self._radius, d) self._stimuli[elem].position = (xy[0] - size[0] / 2, xy[1] - size[1] / 2) if __name__ == "__main__": from expyriment.stimuli._textline import TextLine from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() stims = [] for i in range(0, 25): stims.append(TextLine("A")) stimuluscircle = StimulusCircle(radius=200, stimuli=stims) stimuluscircle.make() stimuluscircle.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygonellipse_defaults.py0000644000175000017500000000032712314561273031650 0ustar oliveroliver# Polygon Ellipse polygonellipse_colour = None # 'None' is experiment_text_colour polygonellipse_line_width = 0 polygonellipse_position = (0, 0) polygonellipse_anti_aliasing = 5 polygonellipse_resolution_factor = 1 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/defaults.py0000644000175000017500000000113712314561273026364 0ustar oliveroliver""" Default settings for stimuli.extras. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugin_defaults(__file__), _importer_functions.import_plugin_defaults_from_home(__file__)]: for _defaults in _plugins: exec(_defaults) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_dotcloud_defaults.py0000644000175000017500000000045012314561273030415 0ustar oliveroliver# DotCloud dotcloud_radius = None # 'None' is maximal size to fit on screen dotcloud_dot_colour = None # 'None' is experiment_text_colour dotcloud_background_colour = None # 'None' is transparent dotcloud_position = (0, 0) dotcloud_rotation = None dotcloud_scaling = None dotcloud_flipping = Nonepython-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_polygonline.py0000644000175000017500000000407412314561273027256 0ustar oliveroliver#!/usr/bin/env python """ A Line stimulus. This module contains a class implementing a line stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import math import defaults from _polygonrectangle import PolygonRectangle import expyriment from expyriment import misc class PolygonLine(PolygonRectangle): """A class implementing a line stimulus.""" def __init__(self, start_position, end_position, line_width, colour=None, anti_aliasing=None): """Create a line between two points. Parameters ---------- start_position : (int, int) start point of the line (x,y) end_position : (int, int) end point of the line (x,y) line_width : int, optional width of the plotted line colour : (int, int, int), optional line colour anti_aliasing : int anti aliasing parameter (good anti_aliasing with 10) """ if colour is None: colour = defaults.polygonline_colour if colour is None: colour = expyriment._active_exp.foreground_colour if anti_aliasing is None: anti_aliasing = defaults.polygonline_anti_aliasing f = misc.geometry.XYPoint(start_position) t = misc.geometry.XYPoint(end_position) d = misc.geometry.XYPoint(t.x - f.x, t.y - f.y) PolygonRectangle.__init__(self, size=(f.distance(t), line_width), colour=colour, anti_aliasing=anti_aliasing) self.native_rotate(math.atan2(d.y, d.x) * 180 / math.pi) self.position[0] = f.x + (d.x / 2) self.position[1] = f.y + (d.y / 2) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() p1 = (-180, 15) p2 = (200, 0) line = PolygonLine(p1, p2, 2) line.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/__init__.py0000644000175000017500000000145612314561273026320 0ustar oliveroliver"""The stimuli extra package. Extra stimuli classes are available directly via expyriment.stimuli.extras.*. (For expample: expyriment.stimuli.extras.DotCloud) """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os import defaults from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugins(__file__), _importer_functions.import_plugins_from_settings_folder(__file__)]: for _plugin in _plugins: try: exec(_plugins[_plugin]) except: print("Warning: Could not import {0}".format( _os.path.dirname(__file__) + _os.sep + _plugin)) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/extras/_stimuluscircle_defaults.py0000644000175000017500000000007112314561273031646 0ustar oliveroliver# StimulusCircle stimuluscircle_background_colour = None python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_picture.py0000644000175000017500000000444312314561273025064 0ustar oliveroliver#!/usr/bin/env python """ A picture stimulus. This module contains a class implementing a picture stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import pygame import expyriment from expyriment.misc import unicode2str import defaults from _visual import Visual class Picture(Visual): """A class implementing a general picture stimulus.""" def __init__(self, filename, position=None): """Create a picture. Parameters ---------- filename : str filename (incl. path) of the picture file position :(int,int), optional position of the stimulus """ if position is None: position = defaults.picture_position Visual.__init__(self, position, log_comment=filename) self._filename = filename if not(os.path.isfile(self._filename)): raise IOError("The picture file '{0}' does not exist".format( unicode2str(filename))) _getter_exception_message = "Cannot set {0} if surface exists!" @property def filename(self): """Getter for filename.""" return self._filename @filename.setter def filename(self, value): """Setter for filename.""" if self.has_surface: raise AttributeError(Picture._getter_exception_message.format( "filename")) else: self._filename = value def _create_surface(self): """Create the surface of the stimulus.""" surface = pygame.image.load(unicode2str(self._filename, fse=True)).convert_alpha() if self._logging: expyriment._active_exp._event_file_log( "Picture,loaded,{0}".format(unicode2str(self._filename)), 1) return surface if __name__ == "__main__": from expyriment import __file__ from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() directory = os.path.dirname(__file__) picture = Picture(os.path.join(directory, "expyriment_logo.png")) picture.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_rectangle.py0000644000175000017500000001065212314561273025354 0ustar oliveroliver#!/usr/bin/env python """ A Rectangle stimulus. This module contains a class implementing a rectangle stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame import defaults from _visual import Visual import expyriment class Rectangle(Visual): """A class implementing a rectangle stimulus.""" def __init__(self, size, colour=None, line_width=None, position=None): """Create a rectangle. Parameters ---------- size : (int, int) size (width, height) of the rectangle line_width : int, optional line width in pixels; 0 will result in a filled rectangle, as does a value < 0 or >= min(size) position : (int, int), optional position of the stimulus colour : (int, int, int), optional colour of the rectangle """ if position is None: position = defaults.rectangle_position Visual.__init__(self, position=position) self._size = size if colour is None: colour = defaults.rectangle_colour if colour is not None: self._colour = colour else: self._colour = expyriment._active_exp.foreground_colour if line_width is None: line_width = defaults.rectangle_line_width elif line_width < 0 or line_width >= min(self._size): line_width = 0 self._line_width = line_width _getter_exception_message = "Cannot set {0} if surface exists!" @property def size(self): """Getter for size.""" return self._size @size.setter def size(self, value): """Setter for size.""" if self.has_surface: raise AttributeError(Rectangle._getter_exception_message.format( "size")) else: self._size = value @property def colour(self): """Getter for colour.""" return self._colour @colour.setter def colour(self, value): """Setter for colour.""" if self.has_surface: raise AttributeError(Rectangle._getter_exception_message.format( "colour")) else: self._colour = value @property def line_width(self): """Getter for line_width.""" return self._line_width @line_width.setter def line_width(self, value): """Setter for line_width.""" if self.has_surface: raise AttributeError(Rectangle._getter_exception_message.format( "line_width")) else: self._line_width = value def _create_surface(self): """Create the surface of the stimulus.""" if self._line_width == 0: surface = pygame.surface.Surface(self._size, pygame.SRCALPHA).convert_alpha() surface.fill(self._colour) else: # Invert colours and use it as colourkey for a temporal surface, # fill the surface and draw a smaller rectangle with colourkey # colour colour = [abs(self._colour[0] - 255), abs(self._colour[1] - 255), abs(self._colour[2] - 255)] surface = pygame.surface.Surface( [x + self._line_width for x in self._size], pygame.SRCALPHA).convert_alpha() tmp = pygame.surface.Surface( [x + self._line_width for x in self._size]).convert() tmp.set_colorkey(colour) tmp.fill(colour) pygame.draw.rect(tmp, self._colour, pygame.Rect( (0, 0), [x + self._line_width for x in self._size])) pygame.draw.rect(tmp, colour, pygame.Rect( (self._line_width, self._line_width), [x - self._line_width for x in self._size])) surface.blit(tmp, (0, 0)) return surface def is_point_inside(self, point_xy): """"DEPRECATED METHOD: Please use 'overlapping_with_position'.""" return self.overlapping_with_position(point_xy) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() rect = Rectangle((20, 200), colour=(255, 0, 255)) rect.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_line.py0000644000175000017500000001423612314561273024341 0ustar oliveroliver#!/usr/bin/env python """ A Line stimulus. This module contains a class implementing a line stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import math import pygame import defaults from _visual import Visual import expyriment from expyriment import misc from expyriment.misc._timer import get_time class Line(Visual): """A class implementing a line stimulus.""" def __init__(self, start_point, end_point, line_width, colour=None, anti_aliasing=None): """Create a line between two points. Parameters ---------- start_point : (int, int) start point of the line (x,y) end_point : (int, int) end point of the line (x,y) line_width : int width of the plotted line colour : (int, int, int), optional line colour (int, int, int) anti_aliasing : int, optional anti aliasing parameter (good anti_aliasing with 10) """ self._start_point = list(start_point) self._end_point = list(end_point) self._line_width = line_width Visual.__init__(self, position=[0,0]), if colour is None: colour = defaults.line_colour if colour is None: colour = expyriment._active_exp.foreground_colour self._colour = colour if anti_aliasing is not None: self._anti_aliasing = anti_aliasing else: self._anti_aliasing = defaults.line_anti_aliasing s = misc.geometry.XYPoint(start_point) e = misc.geometry.XYPoint(end_point) d = misc.geometry.XYPoint(e.x - s.x, e.y - s.y) self._position[0] = s.x + (d.x / 2) self._position[1] = s.y + (d.y / 2) _getter_exception_message = "Cannot set {0} if surface exists!" @property def start_point(self): """Getter for start_point.""" return self._start_point @property def end_point(self): """Getter for end_point.""" return self._end_point @property def line_width(self): """Getter for line_width.""" return self._line_width @line_width.setter def line_width(self, value): """Setter for line_width.""" if self.has_surface: raise AttributeError(Line._getter_exception_message.format( "line_width")) else: self._line_width = value @property def colour(self): """Getter for colour.""" return self._colour @colour.setter def colour(self, value): """Setter for colour.""" if self.has_surface: raise AttributeError(Line._getter_exception_message.format( "colour")) else: self._colour = value @property def anti_aliasing(self): """Getter for anti_aliasing.""" return self._anti_aliasing @anti_aliasing.setter def anti_aliasing(self, value): """Setter for anti_aliasing.""" if self.has_surface: raise AttributeError(Line._getter_exception_message.format( "anti_aliasing")) self._anti_aliasing = value @property def position(self): """Getter for position.""" return self._position @position.setter def position(self, value): """Setter for position.""" offset_x = value[0] - self._position[0] offset_y = value[1] - self._position[1] self._position = list(value) self._start_point[0] = self._start_point[0] + offset_x self._start_point[1] = self._start_point[1] + offset_y self._end_point[0] = self._end_point[0] + offset_x self._end_point[1] = self._end_point[1] + offset_y def move(self, offset): """Moves the stimulus in 2D space. Parameters ---------- offset : list, optional translation along x and y axis Returns ------- time : int the time it took to execute this method Notes ----- When using OpenGL, this can take longer then 1ms! """ start = get_time() moved = False x = offset[0] y = offset[1] if x > 0 or x < 0: self._position[0] = self._position[0] + x moved = True if y > 0 or y < 0: self._position[1] = self._position[1] + y moved = True if moved and self._ogl_screen is not None: self._ogl_screen.refresh_position() self._start_point[0] = self._start_point[0] + x self._start_point[1] = self._start_point[1] + y self._end_point[0] = self._end_point[0] + x self._end_point[1] = self._end_point[1] + y return int((get_time() - start) * 1000) def _create_surface(self): """Create the surface of the stimulus.""" s = misc.geometry.XYPoint(self._start_point) e = misc.geometry.XYPoint(self._end_point) d = misc.geometry.XYPoint(e.x - s.x, e.y - s.y) aa_scaling = int((self._anti_aliasing / 5.0) + 1) if self._anti_aliasing > 0: surface = pygame.surface.Surface((s.distance(e)*aa_scaling, self._line_width*aa_scaling), pygame.SRCALPHA).convert_alpha() else: surface = pygame.surface.Surface((s.distance(e), self._line_width), pygame.SRCALPHA).convert_alpha() surface.fill(self._colour) surface = pygame.transform.rotate(surface, math.atan2(d.y, d.x) * 180 / math.pi) if self._anti_aliasing > 0: size = surface.get_size() surface = pygame.transform.smoothscale(surface, (int(size[0] / aa_scaling), int(size[1] / aa_scaling))) return surface if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() p1 = (-180, 15) p2 = (200, 0) line = Line(p1, p2, 2) line.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_textline.py0000644000175000017500000002032112314561273025236 0ustar oliveroliver#!/usr/bin/env python """ A text line stimulus. This module contains a class implementing a text line stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import pygame import defaults from _visual import Visual from expyriment.misc import find_font, unicode2str, str2unicode import expyriment class TextLine(Visual): """A class implementing a single text line.""" def __init__(self, text, position=None, text_font=None, text_size=None, text_bold=None, text_italic=None, text_underline=None, text_colour=None, background_colour=None): """Create a text line stimulus. NOTE: text_font can be both, a name or path to a font file! When text_font is a name, Expyriment will try to find a font that best matches the given name. If no matching font can be found, or if the given font file cannot be found, the Pygame system default will be used. In any case the value of the attribute text_font will always resemble the font that is actually in use! Parameters ---------- text : str text to show (str) position : (int, int), optional position of the stimulus text_font : str, optional font to use as name or as path to a font file text_size : int, optional text size text_bold : bool, optional font should be bold text_italic : bool, optional font should be italic text_underline : bool, optional font should get an underline text_colour : (int, int, int), optional text colour background_colour : (int, int, int), optional background colour """ pygame.font.init() if position is None: position = defaults.textline_position Visual.__init__(self, position, log_comment=text) self._text = text if text_size is None: text_size = defaults.textline_text_size if text_size is not None: self._text_size = text_size else: self._text_size = expyriment._active_exp.text_size if text_font is None: text_font = defaults.textline_text_font if text_font is not None: self._text_font = find_font(text_font) else: self._text_font = find_font(expyriment._active_exp.text_font) try: _font = pygame.font.Font(unicode2str(self._text_font, fse=True), 10) _font = None except: raise IOError("Font '{0}' not found!".format(text_font)) if text_bold is not None: self._text_bold = text_bold else: self._text_bold = defaults.textline_text_bold if text_italic is not None: self._text_italic = text_italic else: self._text_italic = defaults.textline_text_italic if text_underline is not None: self._text_underline = text_underline else: self._text_underline = defaults.textline_text_underline if text_colour is None: text_colour = defaults.textline_text_colour if text_colour is not None: self._text_colour = text_colour else: self._text_colour = expyriment._active_exp.foreground_colour if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ defaults.textline_background_colour _getter_exception_message = "Cannot set {0} if surface exists!" @property def text(self): """Getter for text.""" return self._text @text.setter def text(self, value): """Setter for text.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "text")) else: self._text = value @property def text_font(self): """Getter for text_font.""" return self._text_font @text_font.setter def text_font(self, value): """Setter for text_font.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "text_font")) else: self._text_font = value @property def text_size(self): """Getter for text_size.""" return self._text_size @text_size.setter def text_size(self, value): """Setter for text_size.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "text_size")) else: self._text_size = value @property def text_bold(self): """Getter for text_bold.""" return self._text_bold @text_bold.setter def text_bold(self, value): """Setter for text_bold.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "text_bold")) else: self._text_bold = value @property def text_italic(self): """Getter for text_italic.""" return self._text_italic @text_italic.setter def text_italic(self, value): """Setter for text_italic.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "text_italic")) else: self._text_italic = value @property def text_underline(self): """Getter for text_underline.""" return self._text_underline @text_underline.setter def text_underline(self, value): """Setter for text_underline.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "text_underline")) else: self._text_underline = value @property def text_colour(self): """Getter for text_colour.""" return self._text_colour @text_colour.setter def text_colour(self, value): """Setter for text_colour.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "text_colour")) else: self._text_colour = value @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @background_colour.setter def background_colour(self, value): """Setter for background_colour.""" if self.has_surface: raise AttributeError(TextLine._getter_exception_message.format( "background_colour")) else: self._background_colour = value def _create_surface(self): """Create the surface of the stimulus.""" if os.path.isfile(self._text_font): _font = pygame.font.Font(unicode2str(self._text_font, fse=True), self._text_size) else: _font = pygame.font.Font(self._text_font, self._text_size) _font.set_bold(self.text_bold) _font.set_italic(self.text_italic) _font.set_underline(self.text_underline) if type(self.text) is not unicode: # Pygame wants latin-1 encoding here for character strings _text = str2unicode(self.text).encode('latin-1') else: _text = self.text if self.background_colour: text = _font.render(_text, True, self.text_colour, self.background_colour) else: text = _font.render(_text, True, self.text_colour) text.convert_alpha() surface = text return surface if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() textline = TextLine("abcde fghijk lmnopqrstuvwxyz 12 348 56789", text_font="Helvetica", text_size=20, text_bold=False) textline.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_fixcross.py0000644000175000017500000000574512314561273025257 0ustar oliveroliver#!/usr/bin/env python """ A fixation cross stimulus. This module contains a class implementing a fixation cross stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from _shape import Shape import expyriment class FixCross(Shape): """A class implementing a general fixation cross.""" def __init__(self, size=None, position=None, line_width=None, colour=None, anti_aliasing=None, cross_size=None): """Create a fixation cross. Parameters ---------- size : (int, int), optional size of the cross position : (int, int), optional position of the stimulus line_width : (int, int), optional width of the lines colour : (int, int), optional colour of the cross anti_aliasing : (int, int), optional anti aliasing parameter cross_size : (int, int) DEPRECATED argument please use 'size' and specify x and y dimensions. """ if cross_size is not None and size is None: size = (cross_size, cross_size) if position is None: position = defaults.fixcross_position if colour is None: colour = defaults.fixcross_colour if colour is not None: self._colour = colour else: self._colour = expyriment._active_exp.foreground_colour if anti_aliasing is None: anti_aliasing = defaults.fixcross_anti_aliasing Shape.__init__(self, position=position, colour=colour, anti_aliasing=anti_aliasing) if size is None: size = defaults.fixcross_size if line_width is None: line_width = defaults.fixcross_line_width self._size = size self._line_width = line_width x = (self._size[0] - line_width) / 2 y = (self._size[1] - line_width) / 2 self.add_vertex((line_width, 0)) self.add_vertex((0, -y)) self.add_vertex((x, 0)) self.add_vertex((0, -line_width)) self.add_vertex((-x, 0)) self.add_vertex((0, -y)) self.add_vertex((-line_width , 0)) self.add_vertex((0, y)) self.add_vertex((-x, 0)) self.add_vertex((0, line_width)) self.add_vertex((x, 0)) @property def size(self): """Getter for size.""" return self._size @property def cross_size(self): """DEPRECATED getter, please use size""" return self.size @property def line_width(self): """Getter for line_width.""" return self._line_width if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() fixcross = FixCross(size=(100, 100)) fixcross.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_shape.py0000644000175000017500000004405312314561273024512 0ustar oliveroliver#!/usr/bin/env python """ A Shape stimulus. This module contains a class implementing a shape stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import copy from math import sqrt import pygame import defaults from _visual import Visual import expyriment from expyriment.misc._timer import get_time class Shape(Visual): """A class implementing a shape.""" def __init__(self, position=None, colour=None, line_width=None, anti_aliasing=None): """Create a shape. A shape comprises always (0,0) as origin vertex Parameters ---------- position : (int, int), optional position of the stimulus colour : (int, int, int), optional colour of the shape line_width : int, optional line width in pixels; 0 will result in a filled shape, as does a value < 0 or >= min(size) (optional) anti_aliasing : int, optional anti aliasing parameter (good anti_aliasing with 10) """ if position is None: position = defaults.shape_position Visual.__init__(self, position) if colour is None: colour = defaults.shape_colour if colour is not None: self._colour = colour else: self._colour = expyriment._active_exp.foreground_colour if line_width is not None: self._line_width = line_width else: self._line_width = defaults.shape_line_width if anti_aliasing is not None: self._anti_aliasing = anti_aliasing else: self._anti_aliasing = defaults.shape_anti_aliasing self._vertices = [] self._xy_points = [] self._rect = [] self._native_rotation = 0 self._native_scaling = [1, 1] self._native_rotation_centre = (0, 0) self._rotation_centre_display_colour = None self._update_points() _getter_exception_message = "Cannot perform {0} if surface exists!" def __repr__(self): return "vertices: {0}; points: {1}".format(self.vertices, self.points) @property def colour(self): """Getter for colour.""" return self._colour @colour.setter def colour(self, colour): """Setter for colour.""" if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "colour change")) self._colour = colour @property def anti_aliasing(self): """Getter for anti_aliasing.""" return self._anti_aliasing @anti_aliasing.setter def anti_aliasing(self, value): """Setter for anti_aliasing.""" if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "anti_aliasing")) self._anti_aliasing = value @property def rotation_centre(self): """Getter for rotation_centre.""" return self._native_rotation_centre @rotation_centre.setter def rotation_centre(self, centre): """Setter for rotation_centre.""" self._native_rotation_centre = centre self._update_points() @property def rotation_centre_display_colour(self): """Getter for rotation_centre_display_colour.""" return self._rotation_centre_display_colour @rotation_centre_display_colour.setter def rotation_centre_display_colour(self, colour): """Setter for rotation_centre_display_colour. Set rotation_centre_display_colour to a colour (default=None) to display the centre of rotation. """ self._rotation_centre_display_colour = colour self._update_points() @property def width(self): return self.rect[3] - self.rect[1]#r-l @property def height(self): return self.rect[0] - self.rect[2]#t-b @property def size(self): return (self.width, self.height) @property def line_width(self): return self._line_width @property def rect(self): """Getter for rect =(top, left, bottom, right).""" return self._rect @property def vertices(self): """Getter for the polygon verticies.""" return self._vertices @property def points(self): """Return polygon as list of tuples (x,y) in Expyriment coordinates. In contrast to the vertex representation, the point representation takes into all the native transformations (rotation, scaling, flipping) Returns ------- val: list of tuples polygon as list of tuples (x,y) in Expyriment coordinates """ rtn = [] for p in self.xy_points: rtn.append(p.tuple) return rtn @property def points_on_screen(self): """Return polygon as list of tuples in Expyriment coordinates. In contrast to the vertex representation, the point representation takes into all the native transformations (rotation, scaling, flipping) Returns ------- val: list of tuples polygon as list of tuples (x,y) in Expyriment coordinates """ rtn = [] for p in self.xy_points_on_screen: rtn.append(p.tuple) return rtn @property def scaling(self): """"Getter for the total native scaling.""" return self._native_scaling @property def rotation(self): """"Getter for the total native rotation.""" return self._native_rotation @property def flipping(self): """"Getter for the total native flipping.""" return ((self.scaling[0] < 0), (self.scaling[1] < 0)) @property def xy_points(self): """Return polygon as list of XYPoints of the shape. The representation does not take into account the position. Use xy_points_on_screen for position-depended representation. In contrast to the vertex representation, the point representation takes into all the native transformations (rotation, scaling, flipping). Returns ------- val: list of XYPoints polygon as list of XYPoints of the shape """ return self._xy_points @property def xy_points_on_screen(self): """Return polygon as list of XYPoints in Expyriment coordinates. In contrast to the vertex representation, the point representation takes into all the native transformations (rotation, scaling, flipping). Returns ------- val: list of XYPoints polygon as list of XYPoints of the shape """ rtn = [] pos = expyriment.misc.geometry.XYPoint(xy=self.position) for p in copy.deepcopy(self.xy_points): rtn.append(p.move(pos)) return rtn def add_vertex(self, xy): """ Add a vertex to the shape. Parameters ---------- xy : (int, int) vertex as tuple """ self.add_vertices([xy]) def add_vertices(self, vertex_list): """ Add a list of vertices to the shape. Parameters ---------- vertex_list : (int, int) list of vertices (int, int) """ type_error_message = "The method add_vertices requires a list of" + \ " tuples as argument." if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "add_vertices")) if type(vertex_list) is not list: raise TypeError(type_error_message) for xy in vertex_list: if type(xy) is not tuple and type(xy) is not list: raise TypeError(type_error_message) if len(xy) != 2: raise TypeError(type_error_message) self._vertices.append(list(xy)) self._update_points() def remove_vertex(self, index): """Remove a vertex.""" if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "remove_vertex")) if index > 0 and index < len(self._vertices): self._vertices.pop(index) self._update_points() def erase_vertices(self): """Removes all vertices.""" if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "erase_vertices")) self._vertices = [] self._xy_points = [] self._rect = [] self._native_rotation = 0 self._native_scaling = [1, 1] self._native_rotation_centre = (0, 0) self._rotation_centre_display_colour = None self._update_points() def convert_expyriment_xy_to_surface_xy(self, point_xy): """Convert a point from shape coordinates to surface coordinates. Parameters ---------- point_xy : (int, int) Expyriment screen coordinates (tuple) """ jitter = self._line_width / 2 return (int(point_xy[0] - self.rect[1] + jitter), - 1 * int(point_xy[1] - self.rect[0] - jitter)) def native_overlapping_with_position(self, position): """Return True if the position is inside the shape. Parameters position -- Expyriment screen coordinates (tuple) Returns ------- val : bool True if the position is inside the shape """ pt = expyriment.misc.geometry.XYPoint(position) return pt.is_inside_polygon(self.xy_points_on_screen) def is_point_inside(self, point_xy): """DEPRECATED METHOD: Please use 'native_overlapping_with_position'.""" return self.native_overlapping_with_position(point_xy) def overlapping_with_shape(self, other): """Return true if shape overlaps with other shape. Parameters ---------- other : stimuli.Shape the other shape object Returns ------- val : bool True if overlapping """ # shape and other shape do not overlap if # (a) no point of shape is inside other shape # (b) AND no point of other shape is inside shape # (c) AND lines do not intersect s1 = self.xy_points_on_screen s2 = other.xy_points_on_screen #(a) & (b) for p in s1: if p.is_inside_polygon(s2): return True for p in s2: if p.is_inside_polygon(s1): return True #(c) for from1 in range(0, len(s1) - 1): for to1 in range(from1 + 1, len(s1)): for from2 in range(0, len(s2) - 1): for to2 in range(0, len(s2)): if expyriment.misc.geometry.lines_intersect(s1[from1], s1[to1], s2[from2], s2[to2]): return True return False def is_shape_overlapping(self, shape2): """DEPRECATED METHOD: Please use 'overlapping_with_shape'.""" return self.overlapping_with_shape(shape2) def native_rotate(self, degree): """Rotate the shape. Native rotation of shape is a native operation (not a surface operation) and does therefore not go along with a quality loss. No surface will be created. Parameters ---------- degree : int degree to rotate counterclockwise (int) """ if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "native_rotate")) self._native_rotation = self._native_rotation + degree self._update_points() def native_scale(self, factors, scale_line_width=False): """Scale the shape. Native scaling of shapes is a native operation (not a surface operation) and does therefore not go along with a quality loss. No surface will be created. Negative scaling values will native_flip the stimulus. Parameters ---------- factors : int or (int, int) x and y factors to scale scale_line_width : bool, optional if True, line_width will be scaled proportionally to the change in surface size (default=False) """ if (type(factors) is not list): factors = [factors, factors] if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "native_scale")) self._native_scaling[0] = self._native_scaling[0] * factors[0] self._native_scaling[1] = self._native_scaling[1] * factors[1] self._line_width = self._line_width * sqrt(factors[0] * factors[1]) self._update_points() def native_flip(self, booleans): """Flip the shape. Native flipping of shapes is a native operation (not a surface operation) and does therefore not go along with a quality loss. No surface will be created. Parameters ---------- booleans : (bool, bool) booleans to flip horizontally and vertically or not """ if self.has_surface: raise AttributeError(Shape._getter_exception_message.format( "native_flip")) if booleans[0]: self._native_scaling[0] = self._native_scaling[0] * -1 if booleans[1]: self._native_scaling[1] = self._native_scaling[1] * -1 self._update_points() def blur(self, level): """Blur the shape. This blurs the stimulus, by scaling it down and up by the factor of 'level'. Notes ----- Depending on the blur level and the size of your stimulus, this method may take some time! Parameters ---------- level : int level of bluring Returns ------- time : int the time it took to execute this method """ start = get_time() self.scale((1.0 / level, 1.0 / level)) self.scale((level, level)) return int((get_time() - start) * 1000) def _update_points(self): """Updates the points of the shape and the drawing rect. Converts vertex to points, centers points, rotates, calculates rect and clears surface and draw_rotation_point. """ # Copying and scaling and flipping of verticies tmp_vtx = [] for v in self._vertices: v = (v[0] * self._native_scaling[0], v[1] * self._native_scaling[1]) tmp_vtx.append(v) # Converts tmp_vtx to points in xy-coordinates xy_p = [expyriment.misc.geometry.XYPoint(0, 0)] for v in tmp_vtx: x = (v[0] + xy_p[-1].x) y = (v[1] + xy_p[-1].y) xy_p.append(expyriment.misc.geometry.XYPoint(x, y)) xy_p = self._center_points(xy_p) if self._native_rotation != 0: for x in range(0, len(xy_p)): xy_p[x].rotate(self._native_rotation, self._native_rotation_centre) self._xy_points = xy_p self._rect = self._make_shape_rect(self.xy_points) def _make_shape_rect(self, points): """Four points (geomerty.XYPoint) top, left, bottom, right.""" t = 0 l = 0 r = 0 b = 0 for p in points: if p.x < l: l = p.x elif p.x > r: r = p.x if p.y > t: t = p.y elif p.y < b: b = p.y return (t, l, b, r) def _center_points(self, points): """Return centered points (list of geomerty.XYPoint).""" t, l, b, r = self._make_shape_rect(points) # Stimulus center c = expyriment.misc.geometry.XYPoint(((r - l) / 2.0) - r, ((t - b) / 2.0) - t) for x in range(0, len(points)): # Center points points[x].move(c) return points def _create_surface(self): """Create the surface of the stimulus.""" # Trick: draw enlarged shape and reduce size if self._anti_aliasing > 0: # Draw enlarged shape aa_scaling = (self._anti_aliasing / 5.0) + 1 old_scaling = copy.copy(self._native_scaling) old_line_width = self._line_width self.native_scale([aa_scaling, aa_scaling], scale_line_width=True) line_width = int(self._line_width) # Draw the rect s = (self.size[0] + line_width, self.size[1] + line_width) surface = pygame.surface.Surface(s, pygame.SRCALPHA).convert_alpha() #surface.fill((255, 0, 0)) # for debugging only poly = [] for p in self.xy_points: # Convert points_in_pygame_coordinates poly.append(self.convert_expyriment_xy_to_surface_xy(p.tuple)) rot_centre = self.convert_expyriment_xy_to_surface_xy( self._native_rotation_centre) pygame.draw.polygon(surface, self.colour, poly, line_width) if self._rotation_centre_display_colour is not None: pygame.draw.circle(surface, self._rotation_centre_display_colour, rot_centre, 2) if self._anti_aliasing > 0: # Scale back size = surface.get_size() surface = pygame.transform.smoothscale(surface, (int(size[0] / aa_scaling), int(size[1] / aa_scaling))) self._native_scaling = old_scaling self._line_width = old_line_width self._update_points() return surface if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() sh = Shape(position=(20, 200), colour=(255, 0, 255)) sh.add_vertex((0, 0)) sh.add_vertex((50, 50)) sh.add_vertex((0, 50)) sh.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_textscreen.py0000644000175000017500000003662212314561273025601 0ustar oliveroliver#!/usr/bin/env python """ A text screen stimulus. This module contains a class implementing a text screen stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame import defaults from _visual import Visual from _textline import TextLine from _textbox import TextBox from expyriment.misc import find_font, unicode2str import expyriment class TextScreen(Visual): """A class implementing a screen with heading and text.""" def __init__(self, heading, text, position=None, heading_font=None, heading_size=None, heading_bold=None, heading_italic=None, heading_underline=None, heading_colour=None, text_font=None, text_size=None, text_bold=None, text_italic=None, text_underline=None, text_colour=None, text_justification=None, background_colour=None, size=None): """Create a text screen. Parameters ---------- heading : str heading of the text screen text : str text of the text screen position : (int, int), optional position of the stimulus heading_font : str, optional heading font to use heading_size : int, optional heading font size heading_bold : bool, optional heading should be bold heading_italic : bool, optional heading should be italic heading_underline : bool, optional heading should get an underline heading_colour : (int,int,int), optional heding colour text_font : str, optional text font to use text_size : int, optional text font size text_bold : bool, optional text should be bold text_italic : bool, optional text should be italic text_underline : bool, optional text should get an underline text_colour : (int,int,int), optional text colour text_justification : int, optional 0 (Left), 1(center), 2(right) (int) (optional) background_colour : (int, int, int), optional background_colour size : (int, int), optional size of the text screen """ if position is None: position = defaults.textscreen_position Visual.__init__(self, position, log_comment="text_screen") self._heading = heading self._text = text if heading_font is None: heading_font = defaults.textscreen_heading_font if heading_font is not None: self._heading_font = find_font(heading_font) else: self._heading_font = find_font(expyriment._active_exp.text_font) try: _font = pygame.font.Font( unicode2str(self._heading_font, fse=True), 10) _font = None except: raise IOError("Font '{0}' not found!".format(heading_font)) if heading_size is None: heading_size = defaults.textscreen_heading_size if heading_size: self._heading_size = heading_size else: self._heading_size = int(expyriment._active_exp.text_size * 1.2) if heading_bold is not None: self._heading_bold = heading_bold else: self._heading_bold = defaults.textscreen_heading_bold if heading_italic is not None: self._heading_italic = heading_italic else: self._heading_italic = \ defaults.textscreen_heading_italic if heading_underline is not None: self._heading_underline = heading_underline else: self._heading_underline = \ defaults.textscreen_heading_underline if heading_colour is None: heading_colour = defaults.textscreen_heading_colour if heading_colour is not None: self._heading_colour = heading_colour else: self._heading_colour = expyriment._active_exp.foreground_colour if text_font is None: text_font = defaults.textscreen_text_font if text_font is not None: self._text_font = find_font(text_font) else: self._text_font = find_font(expyriment._active_exp.text_font) try: _font = pygame.font.Font(unicode2str(self._text_font, fse=True), 10) _font = None except: raise IOError("Font '{0}' not found!".format(text_font)) if text_size is None: self._text_size = defaults.textscreen_text_size if text_size is not None: self._text_size = text_size else: self._text_size = expyriment._active_exp.text_size if text_bold is not None: self._text_bold = text_bold else: self._text_bold = defaults.textscreen_text_bold if text_italic is not None: self._text_italic = text_italic else: self._text_italic = defaults.textscreen_text_italic if text_underline is not None: self._text_underline = text_underline else: self._text_underline = defaults.textscreen_text_underline if text_colour is None: text_colour = defaults.textscreen_text_colour if text_colour is not None: self._text_colour = text_colour else: self._text_colour = expyriment._active_exp.foreground_colour if text_justification is not None: self._text_justification = text_justification else: self._text_justification = \ defaults.textscreen_text_justification if size is not None: self._size = size else: size = defaults.textscreen_size if size is None: try: self._size = ( expyriment._active_exp.screen.surface.get_size()[0] - expyriment._active_exp.screen.surface.get_size()[0] / 5, expyriment._active_exp.screen.surface.get_size()[1] - expyriment._active_exp.screen.surface.get_size()[1] / 5) except: raise RuntimeError("Cannot get size of screen!") if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ defaults.textscreen_background_colour _getter_exception_message = "Cannot set {0} if surface exists!" @property def heading(self): """Getter for heading.""" return self._heading @heading.setter def heading(self, value): """Setter for heading.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "heading")) else: self._heading = value @property def text(self): """Getter for text.""" return self._text @text.setter def text(self, value): """Setter for text.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text")) else: self._text = value @property def text_font(self): """Getter for text_font.""" return self._text_font @text_font.setter def text_font(self, value): """Setter for text_font.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text_font")) else: self._text_font = value @property def text_size(self): """Getter for text_size.""" return self._text_size @text_size.setter def text_size(self, value): """Setter for text_size.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text_size")) else: self._text_size = value @property def text_bold(self): """Getter for text_bold.""" return self._text_bold @text_bold.setter def text_bold(self, value): """Setter for text_bold.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text_bold")) else: self._text_bold = value @property def text_italic(self): """Getter for text_italic.""" return self._text_italic @text_italic.setter def text_italic(self, value): """Setter for text_italic.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text_italic")) else: self._text_italic = value @property def text_underline(self): """Getter for text_underline.""" return self._text_underline @text_underline.setter def text_underline(self, value): """Setter for text_underline.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text_underline")) else: self._text_underline = value @property def text_colour(self): """Getter for text_colour.""" return self._text_colour @text_colour.setter def text_colour(self, value): """Setter for text_colour.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text_colour")) else: self._text_colour = value @property def heading_font(self): """Getter for heading_font.""" return self._heading_font @heading_font.setter def heading_font(self, value): """Setter for heading_font.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "heading_font")) else: self._heading_font = value @property def heading_size(self): """Getter for heading_size.""" return self._heading_size @heading_size.setter def heading_size(self, value): """Setter for heading_size.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "heading_size")) else: self._heading_size = value @property def heading_bold(self): """Getter for heading_bold.""" return self._heading_bold @heading_bold.setter def heading_bold(self, value): """Setter for heading_bold.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "heading_bold")) else: self._heading_bold = value @property def heading_italic(self): """Getter for heading_italic.""" return self._heading_italic @heading_italic.setter def heading_italic(self, value): """Setter for heading_italic.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "heading_italic")) else: self._heading_italic = value @property def heading_underline(self): """Getter for heading_underline.""" return self._heading_underline @heading_underline.setter def heading_underline(self, value): """Setter for heading_underline.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "heading_underline")) else: self._heading_underline = value @property def heading_colour(self): """Getter for heading_colour.""" return self._heading_colour @heading_colour.setter def heading_colour(self, value): """Setter for heading_colour.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "heading_colour")) else: self._heading_colour = value @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @background_colour.setter def background_colour(self, value): """Setter for background_colour.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "background_colour")) else: self._background_colour = value @property def size(self): """Getter for size.""" return self._size @size.setter def size(self, value): """Setter for size.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "size")) else: self._size = value @property def text_justification(self): """Getter for text_justification.""" return self._text_justification @text_justification.setter def text_justification(self, value): """Setter for text_justification.""" if self.has_surface: raise AttributeError(TextScreen._getter_exception_message.format( "text_justification")) else: self._text_justification = value def _create_surface(self): """Create the surface of the stimulus.""" surface = pygame.surface.Surface(self.size, pygame.SRCALPHA).convert_alpha() if self.background_colour is not None: surface.fill(self.background_colour) header = TextLine(text=self.heading, text_size=self.heading_size, text_colour=self.heading_colour, background_colour=self.background_colour, text_font=self.heading_font, text_bold=self.heading_bold, text_italic=self.heading_italic, text_underline=self.heading_underline) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 box = TextBox(text=self.text, text_font=self.text_font, text_size=self.text_size, text_bold=self.text_bold, text_italic=self.text_italic, text_underline=self.text_underline, text_colour=self.text_colour, background_colour=self.background_colour, size=(self.size[0], self.size[1] - self.size[1] / 5), text_justification=self.text_justification) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 surface.blit(header._get_surface(), (self.size[0] / 2 - header.surface_size[0] / 2, 0)) surface.blit(box._get_surface(), (self.size[0] / 2 - box.size[0] / 2, self.size[1] / 5)) return surface if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() textscreen = TextScreen("Hello World", "Line one.\nLine two.\nLine three.") textscreen.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_dot.py0000644000175000017500000000332712314561273024177 0ustar oliveroliver#!/usr/bin/env python """ A dot stimulus. This module contains a class implementing a dot stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from _circle import Circle class Dot(Circle): """A class implementing a basic 2D dot.""" def __init__(self, radius, colour=None, position=None): """Create a dot. DEPRECATED CLASS: Please use 'Circle'! Parameters ---------- radius : int radius of the dot colour : (int, int, int), optional colour of the dot position : (int, int), optional position of the stimulus """ if position is None: position = defaults.dot_position if colour is None: colour = defaults.dot_colour Circle.__init__(self, diameter=radius*2, colour=colour, position=position) def is_overlapping(self, other, minimal_gap=0): """DEPRECATED METHOD: Please use 'overlapping_with_circle'""" return self.overlapping_with_circle(other, minimal_gap) def is_center_inside(self, other): """DEPRECATED METHOD: Please use 'center_inside_circle'""" return self.center_inside_circle(other) def is_inside(self, other): """DEPRECATED METHOD: Please use 'inside_circle'""" return self.inside_circle(other) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() dot = Dot(radius=100) dot.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_stimulus.py0000644000175000017500000000334412314561273025275 0ustar oliveroliver#!/usr/bin/env python """ This module contains the base classes for stimuli. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from copy import deepcopy import expyriment class Stimulus(expyriment._Expyriment_object): """A class implementing a very general experimental stimulus. All other stimulus classes are based on this one. If a new stimulus is to be created, (at least) this class should be subclassed. """ _id_counter = 0 def __init__(self, log_comment=None): """Create a stimulus. Parameters ---------- log_comment : str, optional comment for the event log file """ expyriment._Expyriment_object.__init__(self) self._id = Stimulus._id_counter Stimulus._id_counter += 1 log_txt = "Stimulus,created,{0},{1}".format(self.id, self.__class__.__name__) if log_comment is not None: log_txt = u"{0},{1}".format(log_txt, log_comment) if self._logging: expyriment._active_exp._event_file_log(log_txt, 2) @property def id(self): """Getter for id.""" return self._id def copy(self): """Return a deep copy of the stimulus.""" copy = deepcopy(self) copy._id = Stimulus._id_counter Stimulus._id_counter += 1 if self._logging: expyriment._active_exp._event_file_log( "Stimulus,created,{0},{1},copied from {2}".format( copy.id, copy.__class__.__name__, self.id)) return copy python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_visual.py0000644000175000017500000012142612314561273024715 0ustar oliveroliver""" This module contains the base classes for visual stimuli. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import tempfile import os import copy import random import types import pygame try: import OpenGL.GLU as oglu import OpenGL.GL as ogl except ImportError: oglu = None ogl = None import defaults import expyriment from _stimulus import Stimulus from expyriment.misc import geometry from expyriment.misc import unicode2str from expyriment.misc._timer import get_time random.seed() class _LaminaPanelSurface(object): """A class implementing an OpenGL surface.""" # The following code is based on part of the Lamina module by David Keeney # (http://pitchersduel.python-hosting.com/file/branches/Lamina/lamina.py) # with some modifications to fit it into expyriment (e.g. positioning) def __init__(self, surface, quadDims=(-1, 1, 1, 1), position=(0, 0)): """Initialize new instance. Parameters ---------- surface : pygame surface pygame surface to convert quadDims : (int,int), optional position : (int,int), optional """ self._txtr = Visual._load_texture(surface) self._winsize = surface.get_size() self._position = position left, top, width, height = quadDims right, bottom = left + width, top - height self._qdims = quadDims self.dims = ((left, top, 0), (right, top, 0), (right, bottom, 0), (left, bottom, 0)) self.refresh_position() def __del__(self): """Call glDeleteTextures when deconstruction the object.""" if self._txtr is not None: try: ogl.glDeleteTextures([self._txtr]) except: pass def convertMousePos(self, pos): """Convert 2d pixel mouse pos to 2d gl units. Parameters ---------- pos : (int, int) position of mouse """ x0, y0 = pos x = x0 / self._winsize[0] * self._qdims[2] + self._qdims[0] y = y0 / self._winsize[1] * self._qdims[3] + self._qdims[1] return x, y def refresh_position(self): """Recalc where in modelspace quad needs to be to fill screen.""" screensize = pygame.display.get_surface().get_size() bottomleft = oglu.gluUnProject(screensize[0] / 2 - \ self._winsize[0] / 2 + \ self._position[0], screensize[1] / 2 - \ self._winsize[1] / 2 + \ self._position[1], 0) bottomright = oglu.gluUnProject(screensize[0] / 2 + \ self._winsize[0] / 2 + \ self._position[0], screensize[1] / 2 - \ self._winsize[1] / 2 + \ self._position[1], 0) topleft = oglu.gluUnProject(screensize[0] / 2 - \ self._winsize[0] / 2 + \ self._position[0], screensize[1] / 2 + \ self._winsize[1] / 2 + \ self._position[1], 0) topright = oglu.gluUnProject(screensize[0] / 2 + \ self._winsize[0] / 2 + \ self._position[0], screensize[1] / 2 + \ self._winsize[1] / 2 + \ self._position[1], 0) self.dims = topleft, topright, bottomright, bottomleft width = topright[0] - topleft[0] height = topright[1] - bottomright[1] self._qdims = topleft[0], topleft[1], width, height def display(self): """Draw surface to a quad.""" ogl.glEnable(ogl.GL_BLEND) ogl.glBlendFunc(ogl.GL_SRC_ALPHA, ogl.GL_ONE_MINUS_SRC_ALPHA) ogl.glEnable(ogl.GL_TEXTURE_2D) ogl.glBindTexture(ogl.GL_TEXTURE_2D, self._txtr) ogl.glTexEnvf(ogl.GL_TEXTURE_ENV, ogl.GL_TEXTURE_ENV_MODE, ogl.GL_REPLACE) ogl.glTexParameterfv(ogl.GL_TEXTURE_2D, ogl.GL_TEXTURE_MIN_FILTER, ogl.GL_LINEAR) ogl.glBegin(ogl.GL_QUADS) ogl.glTexCoord2f(0.0, 1.0) ogl.glVertex3f(*self.dims[0]) ogl.glTexCoord2f(1.0, 1.0) ogl.glVertex3f(*self.dims[1]) ogl.glTexCoord2f(1.0, 0.0) ogl.glVertex3f(*self.dims[2]) ogl.glTexCoord2f(0.0, 0.0) ogl.glVertex3f(*self.dims[3]) ogl.glEnd() ogl.glDisable(ogl.GL_BLEND) ogl.glDisable(ogl.GL_TEXTURE_2D) # End of code bsed on Lamina module class Visual(Stimulus): """A class implementing a general visual stimulus. All other visual stimuli should be subclassed from this class since it entails code for converting Pygame surfaces into OpenGL textures. This allows for having hardware acceleration (including waiting for the vertical retrace) while still being able to manipulate stimuli in an easy way (based on Pygame surfaces). """ # The following code is based on part of the Lamina module by David Keeney # (http://pitchersduel.python-hosting.com/file/branches/Lamina/lamina.py) # with some modifications to fit it into expyriment (e.g. positioning) @staticmethod def _load_texture(surf): """Load surface into texture object. Returns a texture object. Parameters ---------- surf : pygame.Surface object surface to make texture from """ txtr = ogl.glGenTextures(1) textureData = pygame.image.tostring(surf, "RGBA", 1) ogl.glEnable(ogl.GL_TEXTURE_2D) ogl.glBindTexture(ogl.GL_TEXTURE_2D, txtr) width, height = surf.get_size() ogl.glTexImage2D(ogl.GL_TEXTURE_2D, 0, ogl.GL_RGBA, width, height, 0, ogl.GL_RGBA, ogl.GL_UNSIGNED_BYTE, textureData) ogl.glTexParameterf(ogl.GL_TEXTURE_2D, ogl.GL_TEXTURE_MAG_FILTER, ogl.GL_NEAREST) ogl.glTexParameterf(ogl.GL_TEXTURE_2D, ogl.GL_TEXTURE_MIN_FILTER, ogl.GL_NEAREST) ogl.glDisable(ogl.GL_TEXTURE_2D) return txtr # End of code bsed on Lamina module def __init__(self, position=None, log_comment=None): """Create a visual stimulus. Parameters ---------- position : (int,int), optional position of the stimulus log_comment : str, optional comment for the event log file """ Stimulus.__init__(self, log_comment) if position: self._position = list(position) else: self._position = list(defaults.visual_position) self._surface = None self._is_preloaded = False self._parent = None self._ogl_screen = None self._is_compressed = False self._compression_filename = None self._was_compressed_before_preload = None _compression_exception_message = "Cannot call {0} on compressed stimuli!" def __del__(self): """ Clear surface and ogl_screen when when the objects is deconstructed. """ try: self.clear_surface() except: pass if self._compression_filename is not None: try: os.remove(self._compression_filename) except: pass @property def position(self): """Getter for position.""" return self._position @position.setter def position(self, value): """Setter for position. When using OpenGL, this can take longer then 1ms! """ self._position = list(value) if self.is_preloaded and self._ogl_screen is not None: self._ogl_screen.refresh_position() @property def absolute_position(self): """Getter for absolute_position.""" if self._parent: return (self._parent.absolute_position[0] + self.position[0], self._parent.absolute_position[1] + self.position[1]) else: return self.position @property def is_compressed(self): """Getter for is_compressed.""" return self._is_compressed @property def has_surface(self): """Getter for has_surface.""" if self._surface is not None: return True else: return self.is_compressed @property def surface_size(self): """ Getter for surface_size.""" return self._get_surface().get_size() def _create_surface(self): """Get the surface of the stimulus. This method has to be overwritten for all subclasses individually! """ surface = pygame.surface.Surface((0, 0)) return surface def _set_surface(self, surface): """Set the surface. Parameters ---------- surface : pygame surface surface to be set """ if self.is_compressed: return False else: self._surface = surface return True def _get_surface(self): """Get the surface.""" if self._surface: return self._surface else: if self.is_compressed: tmp = pygame.image.load( self._compression_filename).convert_alpha() else: tmp = self._create_surface() return tmp def copy(self): """Deep copy of the visual stimulus. Returns ------- copy : deep copy of self Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ if self.has_surface: surface_backup = self._get_surface().copy() surface_copy = self._get_surface().copy() rtn = Stimulus.copy(self) if self.has_surface: self._surface = surface_backup rtn._surface = surface_copy rtn._is_preloaded = False rtn._ogl_screen = None rtn._is_compressed = False rtn._compression_filename = None if self.is_preloaded: if expyriment._active_exp.screen.open_gl: self._ogl_screen = _LaminaPanelSurface( self._get_surface(), position=self.position) rtn.preload() if self.is_compressed: rtn.compress() rtn._was_compressed_before_preload = \ self._was_compressed_before_preload return rtn def distance(self, other): """Surface center distance. This method computes the distance between the surface center of this and another visual stimulus. Parameters ---------- other : stimulus the other visual stimulus Returns ------- dist : float distance between surface centers """ return geometry.XYPoint( self.position).distance(geometry.XYPoint(other.position)) def move(self, offset): """Moves the stimulus in 2D space. When using OpenGL, this can take longer then 1ms! Parameters ---------- offset : list, optional translation along x and y axis Returns ------- time : int the time it took to execute this method """ start = get_time() moved = False x = offset[0] y = offset[1] if x > 0 or x < 0: self._position[0] = self._position[0] + x moved = True if y > 0 or y < 0: self._position[1] = self._position[1] + y moved = True if moved and self._ogl_screen is not None: self._ogl_screen.refresh_position() return int((get_time() - start) * 1000) def inside_stimulus(self, stimulus, mode="visible"): """Check if stimulus is inside another stimulus. Parameters ---------- stimulus : expyriment stimulus the other stimulus mode : mode (str), optional "visible": based on non-transparent pixels or "rectangle": based on pixels in pygame surface (default = visible") Returns ------- out : bool Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ if mode == "visible": screen_size = expyriment._active_exp.screen.surface.get_size() self_size = self.surface_size other_size = stimulus.surface_size self_pos = ( self.position[0] + screen_size[0] / 2 - self_size[0] / 2, - self.position[1] + screen_size[1] / 2 - self_size[1] / 2) other_pos = ( stimulus.position[0] + screen_size[0] / 2 - other_size[0] / 2, - stimulus.position[1] + screen_size[1] / 2 - other_size[1] / 2) offset = (-self_pos[0] + other_pos[0], -self_pos[1] + other_pos[1]) self_mask = pygame.mask.from_surface(self._get_surface()) other_mask = pygame.mask.from_surface(stimulus._get_surface()) overlap = self_mask.overlap_area(other_mask, offset) if overlap > 0 and overlap == self_mask.count(): return True else: return False elif mode == "surface": screen_size = expyriment._active_exp.screen.surface.get_size() sx = self.absolute_position[0] + screen_size[0] / 2 sy = self.absolute_position[1] + screen_size[1] / 2 selfrect = pygame.Rect((0, 0), self.surface_size) selfrect.center = (sx, sy) ox = stimulus.absolute_position[0] + screen_size[0] / 2 oy = stimulus.absolute_position[1] + screen_size[1] / 2 stimrect = pygame.Rect((0, 0), stimulus.surface_size) stimrect.right = stimrect.right + 1 stimrect.bottom = stimrect.bottom + 1 stimrect.center = (ox, oy) if selfrect.contains(stimrect): return True else: return False def overlapping_with_stimulus(self, stimulus, mode="visible", use_absolute_position=True): """Check if stimulus is overlapping with another stimulus. Parameters ---------- stimulus : expyriment stimulus the other stimulus mode : mode (str), optional "visible": based on non-transparent pixels or "surface": based on pixels in pygame surface (default = visible") use_absolute_position : bool, optional use absolute_position of stimuli (default) instead of position Returns ------- overlapping : bool are stimuli overlapping or not overlap : (int, int) the overlap (x, y) in pixels. If mode is 'surface', the argument will always be None. Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ if mode == "visible": screen_size = expyriment._active_exp.screen.surface.get_size() self_size = self.surface_size other_size = stimulus.surface_size if use_absolute_position: self_pos = (self.absolute_position[0] + screen_size[0] / 2 - self_size[0] / 2, - self.absolute_position[1] + screen_size[1] / 2 - self_size[1] / 2) other_pos = (stimulus.absolute_position[0] + screen_size[0] / 2 - other_size[0] / 2, - stimulus.absolute_position[1] + screen_size[1] / 2 - other_size[1] / 2) else: self_pos = (self.position[0] + screen_size[0] / 2 - self_size[0] / 2, - self.position[1] + screen_size[1] / 2 - self_size[1] / 2) other_pos = (stimulus.position[0] + screen_size[0] / 2 - other_size[0] / 2, - stimulus.position[1] + screen_size[1] / 2 - other_size[1] / 2) offset = (-self_pos[0] + other_pos[0], -self_pos[1] + other_pos[1]) self_mask = pygame.mask.from_surface(self._get_surface()) other_mask = pygame.mask.from_surface(stimulus._get_surface()) overlap = self_mask.overlap_area(other_mask, offset) if overlap > 0: return True, overlap else: return False, overlap elif mode == "surface": screen_size = expyriment._active_exp.screen.surface.get_size() if use_absolute_position: sx = self.absolute_position[0] + screen_size[0] / 2 sy = self.absolute_position[1] + screen_size[1] / 2 ox = stimulus.absolute_position[0] + screen_size[0] / 2 oy = stimulus.absolute_position[1] + screen_size[1] / 2 else: sx = self.position[0] + screen_size[0] / 2 sy = self.position[1] + screen_size[1] / 2 ox = stimulus.position[0] + screen_size[0] / 2 oy = stimulus.position[1] + screen_size[1] / 2 selfrect = pygame.Rect((0, 0), self.surface_size) selfrect.center = (sx, sy) stimrect = pygame.Rect((0, 0), stimulus.surface_size) stimrect.right = stimrect.right + 1 stimrect.bottom = stimrect.bottom + 1 stimrect.center = (ox, oy) if selfrect.colliderect(stimrect): return True, None else: return False, None def overlapping_with_position(self, position, mode="visible", use_absolute_position=True): """Check if stimulus is overlapping with a certain position. Parameters ---------- position : (int, int) position to check for overlapping mode : mode (str), optional "visible": based on non-transparent pixels or "rectangle": based on pixels in pygame surface (default = visible") use_absolute_position : bool, optional use absolute_position of stimulus (default) instead of position Returns ------- overlapping : bool Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ if mode == "visible": screen_size = expyriment._active_exp.screen.surface.get_size() self_size = self.surface_size if use_absolute_position: self_pos = ( (self.absolute_position[0] + screen_size[0] / 2) - self_size[0] / 2, (-self.absolute_position[1] + screen_size[1] / 2) - self_size[1] / 2) else: self_pos = ( (self.position[0] + screen_size[0] / 2) - self_size[0] / 2, (-self.position[1] + screen_size[1] / 2) - self_size[1] / 2) pos = (position[0] + screen_size[0] / 2, - position[1] + screen_size[1] / 2) offset = (int(pos[0] - self_pos[0]), int(pos[1] - self_pos[1])) self_mask = pygame.mask.from_surface(self._get_surface()) overlap = False if 0 <= offset[0] < self_size[0] and 0 <= offset[1] < self_size[1]: overlap = self_mask.get_at(offset) if overlap > 0: overlap = True else: overlap = False return overlap elif mode == "surface": screen_size = expyriment._active_exp.screen.surface.get_size() if use_absolute_position: sx = self.absolute_position[0] + screen_size[0] / 2 sy = self.absolute_position[1] + screen_size[1] / 2 else: sx = self.position[0] + screen_size[0] / 2 sy = self.position[1] + screen_size[1] / 2 selfrect = pygame.Rect((0, 0), self.surface_size) selfrect.center = (sx, sy) p = (position[0] + screen_size[0] / 2, position[1] + screen_size[1] / 2) if selfrect.collidepoint(p): return True else: return False def plot(self, stimulus): """Plot the stimulus on the surface of another stimulus. Use this to plot more than one stimulus and to present them at the same time afterwards by presenting the stimulus on which they were plotted on. Parameters ---------- stimulus : expyriment stimulus stimulus to whose surface should be plotted Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if not stimulus._set_surface(stimulus._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "plot()")) stimulus.unload(keep_surface=True) self._parent = stimulus rect = pygame.Rect((0, 0), self.surface_size) stimulus_surface_size = stimulus.surface_size rect.center = [self.position[0] + stimulus_surface_size[0] / 2, - self.position[1] + stimulus_surface_size[1] / 2] stimulus._get_surface().blit(self._get_surface(), rect) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,plotted,{0},{1}".format(self.id, stimulus.id), 2) return int((get_time() - start) * 1000) def clear_surface(self): """Clear the stimulus surface. Surfaces are automatically created after any surface operation (presenting, plotting, rotating, scaling, flipping etc.) and preloading. If the stimulus was preloaded, this method unloads the stimulus. This method is functionally equivalent with unload(keep_surface=False). Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if self.is_preloaded: self.unload(keep_surface=False) self._is_compressed = False self._set_surface(None) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,surface cleared,{0}".format(self.id), 2) return int((get_time() - start) * 1000) def compress(self): """"Compress the stimulus. This will create a temporary file on the disk where the surface of the stimululs is written to. The surface will now be read from the disk to free memory. Compressed stimuli cannot do surface operations! Preloading comressed stimuli is possible and highly recommended. Depending on the size of the stimulus, this method may take some time to compute! Returns ------- time : int the time it took to execute this method """ start = get_time() if self.is_compressed is False: if self._compression_filename is None: fid, self._compression_filename = tempfile.mkstemp( dir=defaults.tempdir, suffix=".tga") os.close(fid) pygame.image.save(self._get_surface(), self._compression_filename) self._is_compressed = True self._surface = None if self._logging: expyriment._active_exp._event_file_log( "Stimulus,compressed,{0}".format(self.id), 2) return int((get_time() - start) * 1000) def decompress(self): """Decompress the stimulus. This will decompress the stimulus. The surface will now be read from memory again. Depending on the size of the stimulus, this method may take some time to compute! Returns ------- time : int the time it took to execute this method """ start = get_time() if self.is_compressed: self._surface = pygame.image.load( self._compression_filename).convert_alpha() self._is_compressed = False if self._logging: expyriment._active_exp._event_file_log( "Stimulus,decompressed,{0}".format(self.id), 2) return int((get_time() - start) * 1000) def preload(self, inhibit_ogl_compress=False): """Preload the stimulus to memory. This will prepare the stimulus for a fast presentation. In OpenGL mode this method creates an OpenGL texture based on the surface of the stimulus. When OpenGL is switched off, this method will create a surface if it doesn't exists yet. If stimuli are not preloaded manually, this will happen automatically during presentation. However, stimulus presentation will take some time then! Always preload your stimuli when a timing acurate presentation is needed! Parameters ---------- inhibit_ogl_compress : bool, optional inhibits OpenGL stimuli to be automatically compressed (default=False) Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if not expyriment._active_exp.is_initialized: message = "Can't preload stimulus. Expyriment needs to be " + \ "initilized before preloading a stimulus." raise RuntimeError(message) self._was_compressed_before_preload = self.is_compressed if not self.is_preloaded: if expyriment._active_exp.screen.open_gl: self._ogl_screen = _LaminaPanelSurface( self._get_surface(), position=self.position) if not inhibit_ogl_compress: self.compress() else: self.decompress() self._set_surface(self._get_surface()) self._is_preloaded = True if self._logging: expyriment._active_exp._event_file_log( "Stimulus,preloaded,{0}".format(self.id), 2) return int((get_time() - start) * 1000) def unload(self, keep_surface=False): """Unload the stimulus from memory. This will unload preloaded stimuli. In OpenGL mode, this method will remove the reference to the OpenGL texture and the surface (when 'keep_surface' is False). When OpenGL is switched off, the reference to the surface will be removed (when 'keep_surface' is False). Parameters ---------- keep_surface : bool, optional keep the surface after unload (default=False) Returns ------- time : int the time it took to execute this method See Also -------- clear_surface. Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if expyriment._active_exp.screen.open_gl: self._ogl_screen = None if self.is_preloaded and not self._was_compressed_before_preload \ and keep_surface: self.decompress() else: # Pygame surface if self.is_preloaded and self._was_compressed_before_preload \ and keep_surface: self.compress() if self.is_preloaded and self._logging: expyriment._active_exp._event_file_log("Stimulus,unloaded,{0}"\ .format(self.id), 2) if not keep_surface: self._is_compressed = False self._surface = None if self._logging: expyriment._active_exp._event_file_log("Stimulus,surface cleared,{0}"\ .format(self.id), 2) self._is_preloaded = False return int((get_time() - start) * 1000) @property def is_preloaded(self): """Getter for is_preloaded.""" return self._is_preloaded def present(self, clear=True, update=True): """Present the stimulus on the screen. This clears and updates the screen automatically. When not preloaded, depending on the size of the stimulus, this method can take some time to compute! Parameters ---------- clear : bool, optional if True the screen will be cleared automatically (default = True) update : bool, optional if False the screen will be not be updated automatically (default = True) Returns ------- time : int the time it took to execute this method """ if not expyriment._active_exp.is_initialized or\ expyriment._active_exp.screen is None: raise RuntimeError("Cannot not find a screen!") start = get_time() preloading_required = not(self.is_preloaded) if clear: expyriment._active_exp.screen.clear() if preloading_required: # Check if stimulus has surface keep_surface = self.has_surface self.preload(inhibit_ogl_compress=True) if expyriment._active_exp.screen.open_gl: self._ogl_screen.display() else: screen = expyriment._active_exp.screen.surface rect = pygame.Rect((0, 0), self.surface_size) screen_size = screen.get_size() rect.center = [self.position[0] + screen_size[0] / 2, - self.position[1] + screen_size[1] / 2] screen.blit(self._get_surface(), rect) if self._logging: expyriment._active_exp._event_file_log("Stimulus,presented,{0}"\ .format(self.id), 1) if update: expyriment._active_exp.screen.update() if preloading_required: self.unload(keep_surface=keep_surface) return int((get_time() - start) * 1000) def save(self, filename): """Save the stimulus as image. Parameters ---------- filename : str name of the file to write (possible extensions are BMP, TGA, PNG, or JPEG with TGA being the default) Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ parts = filename.split(".") if len(parts) > 1: parts[-1] = parts[-1].lower() else: parts.append("tga") filename = ".".join(parts) pygame.image.save(self._get_surface(), unicode2str(filename)) def picture(self): """Return the stimulus as Picture stimulus. This will create a temporary file on the hard disk where the image is saved to. Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ import _picture fid, location = tempfile.mkstemp(dir=defaults.tempdir, suffix=".tga") os.close(fid) pygame.image.save(self._get_surface(), location) return _picture.Picture(filename=location) def rotate(self, degree): """Rotate the stimulus. This is a surface operation. After this, a surface will be present! Rotating goes along with a quality loss. Thus, rotating an already rotated stimulus is not a good idea. Parameters ---------- degree : int degree to rotate counterclockwise Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "rotate()")) self.unload(keep_surface=True) self._set_surface(pygame.transform.rotate(self._get_surface(), degree)) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,rotated,{0}, degree={1}".format(self.id, degree)) return int((get_time() - start) * 1000) def scale(self, factors): """Scale the stimulus. This is a surface operation. After this, a surface will be present! Negative scaling values will flip the stimulus. Scaling goes along with a quality loss. Thus, scaling an already scaled stimulus is not a good idea. Parameters ---------- factors : (int, int) or (float, float) tuple representing the x and y factors to scale or a single number. In the case of a single number x and y scaling will be the identical (i.e., proportional scaling) Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "scale()")) self.unload(keep_surface=True) flip = [False, False] if type(factors) in [types.IntType, types.FloatType]: factors = [factors, factors] else: factors = list(factors) if factors[0] < 0: flip[0] = True factors[0] = abs(factors[0]) if factors[1] < 0: flip[1] = True factors[1] = abs(factors[1]) self._set_surface(pygame.transform.smoothscale( self._get_surface(), (int(round(self.surface_size[0] * factors[0])), int(round(self.surface_size[1] * factors[1]))))) if True in flip: self.flip(flip) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,sclaed,{0}, factors={1}".format(self.id, factors), 2) return int((get_time() - start) * 1000) def flip(self, booleans): """Flip the stimulus. This is a surface operation. After this, a surface will be present! Parameters ---------- booleans : (bool, bool) booleans to flip or not Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "flip()")) self.unload(keep_surface=True) self._set_surface(pygame.transform.flip(self._get_surface(), booleans[0], booleans[1])) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,flipped,{0}, booleans={1}".format(self.id, booleans), 2) return int((get_time() - start) * 1000) def blur(self, level): """Blur the stimulus. This blurs the stimulus, by scaling it down and up by the factor of 'level'. Parameters ---------- level : int level of bluring Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() self.scale((1.0 / level, 1.0 / level)) self.scale((level, level)) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,blured,{0}, level={1}".format(self.id, level), 2) return int((get_time() - start) * 1000) def scramble(self, grain_size): """Scramble the stimulus. Attention: If the surface size is not a multiple of the grain size, you may loose some pixels on the edge. Parameters ---------- grain_size : int or (int, int) size of a grain (use tuple of integers for different width & height) Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = get_time() if type(grain_size) is int: grain_size = [grain_size, grain_size] # Make Rect list if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "scramble()")) s = self.surface_size source = [] for r in range(s[1] / int(grain_size[1])): for c in range(s[0] / int(grain_size[0])): xy = (c * int(grain_size[0]), r * int(grain_size[1])) source.append(pygame.Rect(xy, grain_size)) # Make copy and shuffle dest = copy.deepcopy(source) random.shuffle(dest) # Create a new surface tmp_surface = pygame.surface.Surface( s, pygame.SRCALPHA).convert_alpha() for n, s in enumerate(source): tmp_surface.blit(self._get_surface(), dest[n], s) self._set_surface(tmp_surface) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,scrambled,{0}, grain_size={1}".format( self.id, grain_size), 2) return int((get_time() - start) * 1000) def add_noise(self, grain_size, percentage, colour): """Add visual noise on top of the stimulus. This function might take very long for large stimuli. Parameters ---------- grain_size : int size of the grains for the noise percentage : int percentage of covered area colour : (int, int, int) colour (RGB) of the noise Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ import _rectangle start = get_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "add_noise()")) self.unload(keep_surface=True) number_of_pixel_x = int(self.surface_size[0] / grain_size) + 1 number_of_pixel_y = int(self.surface_size[1] / grain_size) + 1 seq = range(number_of_pixel_x * number_of_pixel_y) random.seed() random.shuffle(seq) for idx in seq[:int(len(seq) * (percentage) / 100.0)]: x = (idx % number_of_pixel_x) * grain_size x = int(self.surface_size[0] / 2 - grain_size / 2 - x) y = (idx / number_of_pixel_x) * grain_size y = int(self.surface_size[1] / 2 - grain_size / 2 - y) dot = _rectangle.Rectangle(size=(grain_size, grain_size), position=(x, y), colour=colour) dot.plot(self) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,noise added,{0}, grain_size={1}, percentage={2}"\ .format(self.id, grain_size, percentage)) return int((get_time() - start) * 1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_frame.py0000644000175000017500000000567712314561273024515 0ustar oliveroliver#!/usr/bin/env python """ A frame stimulus. This module contains a class implementing a frame stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from _shape import Shape class Frame(Shape): """A class implementing a frame stimulus.""" #FIXME: frames line width are still no symetric. Fixme later! def __init__(self, size, position=None, frame_line_width=None, colour=None, anti_aliasing=None, line_width=None): """Create a frame. Parameters ---------- size : (int, int) size of the frame (xy) position : (int, int), optional position of the stimulus frame_line_width : int, optional width of the frame lines colour : (int, int, int), optional colour of the frame anti_aliasing : int, optional anti aliasing parameter Notes ----- DEPRECATED CLASS: Please use 'Rectangle' with a line_width > 0! """ if position is None: position = defaults.frame_position if colour is None: colour = defaults.frame_colour if anti_aliasing is None: anti_aliasing = defaults.frame_anti_aliasing Shape.__init__(self, position=position, colour=colour, line_width=0, anti_aliasing=anti_aliasing) if frame_line_width is None: frame_line_width = defaults.frame_frame_line_width if line_width is not None: message = "Frame: line_width attribute have been renamed! " +\ "Please use frame_line_width." raise RuntimeError(message) self._frame_size = list(size) self._frame_line_width = frame_line_width l1 = self._frame_size[0] l2 = self._frame_size[1] l3 = int(l1 - (self._frame_line_width * 2.0)) l4 = int(l2 - (self._frame_line_width * 2.0)) self.add_vertex((l1, 0)) self.add_vertex((0, l2)) self.add_vertex((-l1, 0)) self.add_vertex((0, -l2 + self._frame_line_width)) self.add_vertex((self._frame_line_width , 0)) self.add_vertex((0, l4 - 1)) self.add_vertex((l3, 0)) self.add_vertex((0, -l4 + 1)) self.add_vertex((-l3 - self._frame_line_width, 0)) @property def frame_size(self): """Getter for frame_size.""" return self._frame_size @property def frame_line_width(self): """Getter for frame_line_width.""" return self._frame_line_width if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() fixcross = Frame(size=(100, 100), frame_line_width=1, position=(0, 100)) fixcross.present() exp.clock.wait(2000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_blankscreen.py0000644000175000017500000000234312314561273025675 0ustar oliveroliver#!/usr/bin/env python """ A blank screen stimulus. This module contains a class implementing a blank screen stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import expyriment from _canvas import Canvas class BlankScreen(Canvas): """A class implementing a blank screen.""" def __init__(self, colour=None): """Create a blank screen. Parameters ---------- colour : (int,int,int), optional colour of the blank screen """ if colour is not None: self._colour = colour else: self._colour = expyriment._active_exp.background_colour try: size = expyriment._active_exp.screen.surface.get_size() except: raise RuntimeError("Could not get size of screen!") Canvas.__init__(self, size, colour=self._colour) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() blankscreen = BlankScreen() blankscreen.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_ellipse.py0000644000175000017500000001036512314561273025046 0ustar oliveroliver#!/usr/bin/env python """ An ellipse stimulus. This module contains a class implementing an ellipse stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame import defaults from _visual import Visual import expyriment class Ellipse(Visual): """A class implementing a basic 2D ellipse.""" def __init__(self, size, colour=None, line_width=None, position=None): """Create an ellipse. Parameters ---------- size : (int, int) size of the ellipse (major and minor axis) colour : (int, int, int), optional colour of the ellipse line_width : int, optional line width in pixels; 0 will result in a filled ellipse (as does a value < 0 or >= min(size)) position : (int, int), optional position of the stimulus """ if position is None: position = defaults.circle_position Visual.__init__(self, position) self._size = size if colour is None: colour = defaults.circle_colour if colour is not None: self._colour = colour else: self._colour = expyriment._active_exp.foreground_colour if line_width is None: line_width = defaults.circle_line_width elif line_width < 0 or line_width >= min(self._size): line_width = 0 self._line_width = line_width _getter_exception_message = "Cannot set {0} if surface exists!" @property def size(self): """Getter for size.""" return self._size @size.setter def size(self, value): """Setter for size.""" if self.has_surface: raise AttributeError(Ellipse._getter_exception_message.format( "size")) else: self._size = value @property def colour(self): """Getter for colour.""" return self._colour @colour.setter def colour(self, value): """Setter for colour.""" if self.has_surface: raise AttributeError(Ellipse._getter_exception_message.format( "colour")) else: self._colour = value @property def line_width(self): """Getter for line_width.""" return self._line_width @line_width.setter def line_width(self, value): """Setter for line_width.""" if self.has_surface: raise AttributeError(Ellipse._getter_exception_message.format( "line_width")) else: self._line_width = value def _create_surface(self): """Create the surface of the stimulus.""" if self._line_width == 0: surface = pygame.surface.Surface( self._size, pygame.SRCALPHA).convert_alpha() pygame.draw.ellipse(surface, self._colour, pygame.Rect( (0, 0), self._size)) else: # Invert colours and use it as colourkey for a temporal surface, # fill the surface and draw a smaller ellipse with colourkey colour colour = [abs(self._colour[0] - 255), abs(self._colour[1] - 255), abs(self._colour[2] - 255)] surface = pygame.surface.Surface( [x + self._line_width for x in self._size], pygame.SRCALPHA).convert_alpha() tmp = pygame.surface.Surface( [x + self._line_width for x in self._size]).convert() tmp.set_colorkey(colour) tmp.fill(colour) pygame.draw.ellipse(tmp, self._colour, pygame.Rect( (0, 0), [x + self._line_width for x in self._size])) pygame.draw.ellipse(tmp, colour, pygame.Rect( (self._line_width, self._line_width), [x - self._line_width for x in self._size])) surface.blit(tmp, (0, 0)) return surface if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() ellipse = Ellipse(size=[200, 100]) ellipse.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_circle.py0000644000175000017500000001045112314561273024646 0ustar oliveroliver#!/usr/bin/env python """ A circle stimulus. This module contains a class implementing a circle stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import math import defaults from _ellipse import Ellipse class Circle(Ellipse): """A class implementing a basic 2D circle.""" def __init__(self, diameter, colour=None, line_width=None, position=None): """Create a circle. Parameters ---------- diameter : int diameter of the circle colour : (int,int,int), optional colour of the circle line_width : int, optional line width in pixels; 0 or a value larger the radius will result in a filled circle position : (int, int), optional position of the stimulus """ self._diameter = diameter self._radius = diameter/2.0 if position is None: position = defaults.circle_position if colour is None: colour = defaults.circle_colour if line_width is None: line_width = defaults.circle_line_width elif line_width < 0 or line_width >= self._diameter/2.0: raise AttributeError("line_width must be >= 0 and < diameter/2!") Ellipse.__init__(self, [diameter, diameter], colour, line_width, position) _getter_exception_message = "Cannot set {0} if surface exists!" @property def diameter(self): """Getter for diameter.""" return self._diameter @diameter.setter def diameter(self, value): """Setter for diameter.""" if self.has_surface: raise AttributeError(Circle._getter_exception_message.format( "diameter")) else: self._diameter = value @property def radius(self): """Getter for radius.""" return self._radius def get_polar_coordiantes(self): """Returns tuple with polar coordinates (radial, angle in degrees).""" angle = math.atan2(self._position[1], self._position[0]) angle = angle / math.pi * 180 radial = math.sqrt ((self._position[0] * self._position[0]) + (self._position[1] * self._position[1])) return (radial, angle) def set_polar_coordinates(self, radial, angle_in_degrees): """Set polar coordinates. Parameters ---------- radial : int radial to set angle_in_degrees : float angle of degrees to set """ a = angle_in_degrees / 180.0 * math.pi self._position[0] = radial * math.cos(a) self._position[1] = radial * math.sin(a) def overlapping_with_circle(self, other, minimal_gap=0): """Return True if touching or overlapping with another circle. Parameters ---------- other : expyriment.stimuli.Circle object other circle minimal_gap : int, optional minimum gap between two circle, small gaps will be treated as overlapping (default=0) Returns ------- is_inside : bool """ d = self.distance(other) return (d - minimal_gap <= other._radius + self._radius) def center_inside_circle(self, other): """Return True if the center is inside another circle. Parameters ---------- other : expyriment.stimuli.Circle object other circle Returns ------- is_inside : bool """ d = self.distance(other) return (d <= other._radius) def inside_circle(self, other): """Return True if the whole circle is inside another circle. Parameters ---------- other : expyriment.stimuli.Circle object other circle Returns ------- is_inside : bool """ d = self.distance(other) return (d <= other._radius - self._radius) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() dot = Circle(diameter=100, line_width=3) dot.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_textbox.py0000644000175000017500000003473112314561273025111 0ustar oliveroliver#!/usr/bin/env python """ A text box stimulus. This module contains a class implementing a text box stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import re import pygame import defaults from expyriment.misc import find_font, unicode2str, str2unicode import expyriment from _visual import Visual class TextBox(Visual): """A class implementing a text box with wrapped text. This wraps a given multiline string into a formatted mutliline text box, strips of leading and trailing lines and gets rid of additional whitespaces at beginning of lines (indentation). """ def __init__(self, text, size, position=None, text_font=None, text_size=None, text_bold=None, text_italic=None, text_underline=None, text_justification=None, text_colour=None, background_colour=None): """Create a text box. Notes ----- text_font can be both, a name or path to a font file! When text_font is a name, Expyriment will try to find a font that best matches the given name. If no matching font can be found, or if the given font file cannot be found, the Pygame system default will be used. In any case the value of the attribute text_font will always resemble the font that is actually in use! Parameters ---------- text : str text to wrap size : (int, int) size of the text box position : (int, int), optional position of the stimulus text_font : str, optional text font to use as a name or as a path to a font file text_size : int, optional size of the text text_bold : bool, optional font should be bold text_italic : bool, optional font should be italic text_underline : bool, optional font should get an underline text_justification : int, optional text justification, 0 (left), 1 (center), 2 (right) text_colour : (int, int, int), optional colour of the text background_colour : (int, int, int), optional background colour """ pygame.font.init() if position is None: position = defaults.textbox_position Visual.__init__(self, position) self._text = text self._size = size if text_size is None: text_size = defaults.textbox_text_size if text_size is not None: self._text_size = text_size else: self._text_size = expyriment._active_exp.text_size if text_font is None: text_font = defaults.textbox_text_font if text_font is not None: self._text_font = find_font(text_font) else: self._text_font = find_font(expyriment._active_exp.text_font) try: _font = pygame.font.Font(unicode2str(self._text_font, fse=True), 10) _font = None except: raise IOError("Font '{0}' not found!".format(text_font)) if text_bold is not None: self._text_bold = text_bold else: self._text_bold = defaults.textbox_text_bold if text_italic is not None: self._text_italic = text_italic else: self._text_italic = defaults.textbox_text_italic if text_underline is not None: self._text_underline = text_underline else: self._text_underline = defaults.textbox_text_underline if text_justification is not None: self._text_justification = text_justification else: self._text_justification = defaults.textbox_text_justification if text_colour is not None: self._text_colour = text_colour else: if defaults.textbox_text_colour is not None: self._text_colour = defaults.textbox_text_colour else: self._text_colour = expyriment._active_exp.foreground_colour if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ defaults.textbox_background_colour _getter_exception_message = "Cannot set {0} if surface exists!" @property def text(self): """Getter for text.""" return self._text @text.setter def text(self, value): """Setter for text.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text")) else: self._text = value @property def text_font(self): """Getter for text_font.""" return self._text_font @text_font.setter def text_font(self, value): """Setter for text_font.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text_font")) else: self._text_font = value @property def text_size(self): """Getter for text_size.""" return self._text_size @text_size.setter def text_size(self, value): """Setter for text_size.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text_size")) else: self._text_size = value @property def text_bold(self): """Getter for text_bold.""" return self._text_bold @text_bold.setter def text_bold(self, value): """Setter for text_bold.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text_bold")) else: self._text_bold = value @property def text_italic(self): """Getter for text_italic.""" return self._text_italic @text_italic.setter def text_italic(self, value): """Setter for text_italic.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text_italic")) else: self._text_italic = value @property def text_underline(self): """Getter for text_underline.""" return self._text_underline @text_underline.setter def text_underline(self, value): """Setter for text_underline.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text_underline")) else: self._text_underline = value @property def text_justification(self): """Getter for text_justification.""" return self._text_justification @text_justification.setter def text_justification(self, value): """Setter for text_justification.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text_justification")) else: self._text_justification = value @property def text_colour(self): """Getter for text_colour.""" return self._text_colour @text_colour.setter def text_colour(self, value): """Setter for text_colour.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "text_colour")) else: self._text_colour = value @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @background_colour.setter def background_colour(self, value): """Setter for background_colour.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "background_colour")) else: self._background_colour = value @property def size(self): """Getter for size.""" return self._size @size.setter def size(self, value): """Setter for size.""" if self.has_surface: raise AttributeError(TextBox._getter_exception_message.format( "size")) else: self._size = value def _create_surface(self): """Create the surface of the stimulus.""" rect = pygame.Rect((0, 0), self.size) if os.path.isfile(self._text_font): _font = pygame.font.Font(unicode2str(self._text_font, fse=True), self._text_size) else: _font = pygame.font.Font(self._text_font, self._text_size) _font.set_bold(self.text_bold) _font.set_italic(self.text_italic) _font.set_underline(self.text_underline) if type(self.text) is not unicode: # Pygame wants latin-1 encoding here for character strings _text = str2unicode(self.text).encode('latin-1') else: _text = self.text surface = self.render_textrect(self.format_block(_text), _font, rect, self.text_colour, self.background_colour, self.text_justification) return surface # The following code is taken from the word-wrapped text display module by # David Clark (http://www.pygame.org/pcr/text_rect/index.php). def render_textrect(self, string, font, rect, text_colour, background_colour, justification=0): """Return a surface with reformatted string. The given text string is reformatted to fit within the given rect, word-wrapping as necessary. The text will be anti-aliased. Returns a surface. Parameters ---------- string : str text you wish to render, '\n' begins a new line font : pygame.Font object, optional Font object rect : bool rectstyle giving the size of requested surface text_colour : (int, int, int) text colour background_colour : (int, int, int) background colour justification : int, optional 0 (Left), 1 (center), 2 (right) (int) (default = 0) """ final_lines = [] requested_lines = string.splitlines() # Create a series of lines that will fit on the provided # rect. for requested_line in requested_lines: if font.size(requested_line)[0] > rect.width: words = requested_line.split(' ') # if any of our words are too long to fit, return. for word in words: if font.size(word)[0] >= rect.width: raise Exception( "The word " + word + " is too long to fit in the rect passed.") # Start a new line accumulated_line = "" for word in words: test_line = accumulated_line + word + " " # Build the line while the words fit. if font.size(test_line)[0] < rect.width: accumulated_line = test_line else: final_lines.append(accumulated_line) accumulated_line = word + " " final_lines.append(accumulated_line) else: final_lines.append(requested_line) # Let's try to write the text out on the surface. surface = pygame.surface.Surface(rect.size, pygame.SRCALPHA).convert_alpha() if background_colour is not None: surface.fill(background_colour) accumulated_height = 0 for line in final_lines: # Changed from >= which led to crashes sometimes! if accumulated_height + font.size(line)[1] > rect.height: raise Exception( "Once word-wrapped," + "the text string was too tall to fit in the rect.") if line != "": tempsurface = font.render(line, 1, text_colour) if justification == 0: surface.blit(tempsurface, (0, accumulated_height)) elif justification == 1: surface.blit(tempsurface, ((rect.width - tempsurface.get_width()) / 2, accumulated_height)) elif justification == 2: surface.blit(tempsurface, (rect.width - tempsurface.get_width(), accumulated_height)) else: raise Exception("Invalid justification argument: " + str(justification)) accumulated_height += font.size(line)[1] return surface def format_block(self, block): """Format the given block of text. This function is trimming leading and trailing empty lines and any leading whitespace that is common to all lines. Parameters ---------- block : str block of text to be formatted """ # Separate block into lines #lines = str(block).split('\n') lines = block.split('\n') # Remove leading/trailing empty lines while lines and not lines[0]: del lines[0] while lines and not lines[-1]: del lines[-1] # Look at first line to see how much indentation to trim try: ws = re.match(r'\s*', lines[0]).group(0) except: ws = None if ws: lines = map(lambda x: x.replace(ws, '', 1), lines) # Remove leading/trailing blank lines (after leading ws removal) # We do this again in case there were pure-whitespace lines while lines and not lines[0]: del lines[0] while lines and not lines[-1]: del lines[-1] return '\n'.join(lines) + '\n' # End of code taken from the word-wrapped text display module if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() textbox = TextBox("Line one.\nLine two.\nLine three.", size=(100, 100)) textbox.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_tone.py0000644000175000017500000001376412314561273024364 0ustar oliveroliver#!/usr/bin/env python """ The tone stimulus module. This module contains a class implementing a tone stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import math import wave import struct import itertools import tempfile import shutil import defaults from _audio import Audio class Tone(Audio): """A class implementing a tone stimulus.""" _getter_exception_message = "Cannot set {0} if preloaded!" def __init__(self, duration, frequency=None, samplerate=None, bitdepth=None, amplitude=None): """Create a Tone. Parameters ---------- duration : str duration of the file in ms frequency : int, optional frequency of the sine tone samplerate : int, optional samplerate of the sine tone bitdepth : int, optional bitdeth of the sine tone amplitude : int, optional amplitude of the sine tone """ self._duration = duration / 1000.0 if frequency is None: frequency = defaults.tone_frequency self._frequency = frequency if samplerate is None: samplerate = defaults.tone_samplerate self._samplerate = samplerate if bitdepth is None: bitdepth = defaults.tone_bitdepth self._bitdepth = bitdepth if amplitude is None: amplitude = defaults.tone_amplitude self._amplitude = amplitude filename = self._create_sine_wave() Audio.__init__(self, filename) @property def duration(self): """Getter for duration.""" return self._duration * 1000.0 @duration.setter def duration(self, value): """Setter for duration.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "duration")) else: self._duration = value / 1000 self._filename = self._create_sine_wave() @property def frequency(self): """Getter for frequency.""" return self._frequency @frequency.setter def frequency(self, value): """Setter for frequency.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "frequency")) else: self._frequency = value self._filename = self._create_sine_wave() @property def samplerate(self): """Getter for samplerate.""" return self._samplerate @samplerate.setter def samplerate(self, value): """Setter for samplerate.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "samplerate")) else: self._samplerate = value self._filename = self._create_sine_wave() @property def bitdepth(self): """Getter for bitdepth.""" return self._bitdepth @bitdepth.setter def bitdepth(self, value): """Setter for bitdepth.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "bitdepth")) else: self._bitdepth = value self._filename = self._create_sine_wave() @property def amplitude(self): """Getter for amplitude.""" return self._amplitude @amplitude.setter def amplitude(self, value): """Setter for amplitude.""" if self.is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "amplitude")) else: self._amplitude = value self._filename = self._create_sine_wave() def _grouper(self, n, iterable, fillvalue=None): """Write in chunks.""" args = [iter(iterable)] * n return itertools.izip_longest(fillvalue=fillvalue, *args) def _create_sine_wave(self): """Create the sine wave.""" period = int(self._samplerate / self._frequency) lookup_table = [float(self._amplitude) * \ math.sin(2.0 * math.pi * float(self._frequency) * \ (float(i % period) / float(self._samplerate))) \ for i in xrange(period)] sine = (lookup_table[i % period] for i in itertools.count(0)) channels = ((sine,),) n_samples = self._duration * self._samplerate samples = itertools.islice(itertools.izip( *(itertools.imap(sum, itertools.izip(*channel)) \ for channel in channels)), n_samples) fid, filename = tempfile.mkstemp(dir=defaults.tempdir, prefix="freq{0}_dur{1}_".format(self.frequency, self.duration), suffix=".wav") os.close(fid) w = wave.open(filename, 'w') w.setparams((1, self._bitdepth / 8, self._samplerate, n_samples, 'NONE', 'not compressed')) max_amplitude = float(int((2 ** (self._bitdepth)) / 2) - 1) for chunk in self._grouper(2048, samples): frames = ''.join(''.join(struct.pack( 'h', int(max_amplitude * sample)) for sample in channels) \ for channels in chunk if channels is not None) w.writeframesraw(frames) w.close() return filename def save(self, filename): """Save the sine tone to a file. Parameters ---------- filename : str filename the sine tone should be saved to (str) """ shutil.copy(self._filename, filename) if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 control.start_audiosystem() exp = control.initialize() sine = Tone(duration=1000) sine.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/defaults.py0000644000175000017500000000653612314561273025066 0ustar oliveroliver""" Default settings for the stimuli package. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os import tempfile as _tempfile # Visual visual_position = (0, 0) # Canvas canvas_colour = None # 'None' is transparent canvas_position = (0, 0) # TextLine textline_text_font = None # 'None' is experiment_text_font textline_text_size = None # 'None' is experiment_text_size textline_text_bold = False textline_text_italic = False textline_text_underline = False textline_text_colour = None # 'None' is experiment_text_colour textline_background_colour = None # 'None' is transparent textline_position = (0, 0) # TextBox textbox_text_font = None # 'None' is experiment_text_font textbox_text_size = None # 'None' is experiment_text_size textbox_text_bold = False textbox_text_italic = False textbox_text_underline = False textbox_text_justification = 1 textbox_text_colour = None # 'None' is experiment_text_colour textbox_background_colour = None # 'None' is transparent textbox_position = (0, 0) # TextScreen textscreen_heading_font = None # 'None' is experiment_heading_font textscreen_heading_size = None # 'None' is experiment_heading_size textscreen_heading_bold = False textscreen_heading_italic = False textscreen_heading_underline = False textscreen_heading_colour = None # 'None' is experiment_heading_colour textscreen_text_font = None # 'None' is experiment_text_font textscreen_text_size = None # 'None' is experiment_text_size textscreen_text_bold = False textscreen_text_italic = False textscreen_text_underline = False textscreen_text_colour = None # 'None' is experiment_text_colour textscreen_text_justification = 1 textscreen_background_colour = None # 'None' is transparent textscreen_size = None # 'None' is 4/5 of full screen textscreen_position = (0, 0) # Frame frame_colour = None # 'None' is experiment_text_colour frame_frame_line_width = 5 frame_position = (0, 0) frame_anti_aliasing = 0 # Ellipse ellipse_colour = None # 'None' is experiment_text_colour ellipse_line_width = 0 ellipse_position = (0, 0) ellipse_anti_aliasing = 0 # FixCross fixcross_colour = None # 'None' is experiment_text_colour fixcross_size = (20, 20) fixcross_line_width = 1 fixcross_position = (0, 0) fixcross_anti_aliasing = 0 # Circle circle_colour = None # 'None' is experiment_text_colour circle_position = (0, 0) circle_anti_aliasing = 0 circle_line_width = 0 # Dot dot_colour = None # 'None' is experiment_text_colour dot_position = (0, 0) dot_anti_aliasing = 0 # Shape shape_colour = None # 'None' is experiment_text_colour shape_position = (0, 0) shape_anti_aliasing = 0 shape_line_width = 0 # Line line_colour = None # 'None' is experiment_text_colour line_anti_aliasing = 0 # Rectangle rectangle_colour = None # 'None' is experiment_text_colour rectangle_position = (0, 0) rectangle_line_width = 0 # Picture picture_position = (0, 0) # Video video_position = [0, 0] # Tone tone_frequency = 440 tone_samplerate = 44100 tone_bitdepth = 16 tone_amplitude = 0.5 # Create tmp for compressed stimuli folder tempdir = _tempfile.gettempdir() + "/expyriment" try: _os.mkdir(tempdir) except: pass python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_video.py0000644000175000017500000002425412314561273024521 0ustar oliveroliver""" Video playback. This module contains a class implementing video playback. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os import pygame try: import android.mixer as mixer except: import pygame.mixer as mixer import defaults import _visual from expyriment.io import Keyboard from expyriment.misc import unicode2str import expyriment class Video(_visual.Stimulus): """A class implementing a general video stimulus. This class uses a background thread for playing the video! According to the Pygame documentation MPEG-1 movies are supported. However, it seems that Pygame's video support is quite limited and poor. When the audio from the video should be played as well, the audiosystem has to be stopped (by calling expyriment.control.stop_audiosystem() ) BEFORE the video stimulus is preloaded! After the stimulus has been played the audiosystem can be started again (by calling expyriment.control.start_audiosystem() ). When showing videos in large dimensions, and your computer is not fast enough, frames might be dropped! When using Video.wait_frame() or Video.wait_end(), dropped video frames will be reported and logged. This module will be exchanged with a more capable one in the long run. """ def __init__(self, filename, position=None): """Create a video stimulus. Parameters ---------- filename : str filename (incl. path) of the video position : (int, int), optional position of the stimulus """ _visual.Stimulus.__init__(self, filename) self._filename = filename self._is_preloaded = False self._frame = 0 if position: self._position = position else: self._position = defaults.video_position if not(os.path.isfile(self._filename)): raise IOError("The video file {0} does not exists".format( unicode2str(self._filename))) def __del__(self): """Destructor for the video stimulus.""" self._surface = None self._file = None _getter_exception_message = "Cannot set {0} if preloaded!" @property def is_preloaded(self): """Getter for is_preloaded.""" return self._is_preloaded @property def position(self): """Getter for position.""" return self._position @property def filename(self): """Getter for filename.""" return self._filename @filename.setter def filename(self, value): """Setter for filename.""" if self._is_preloaded: raise AttributeError(Video._getter_exception_message.format( "filename")) else: self._filename = value @property def is_playing(self): """Property to check if movie is playing.""" if self._is_preloaded: return self._file.get_busy() @property def time(self): """Property to get the current playback time.""" if self._is_preloaded: return self._file.get_time() @property def size(self): """Property to get the resolution of the movie.""" if self._is_preloaded: return self._file.get_size() @property def frame(self): """Property to get the current frame.""" if self._is_preloaded: return self._file.get_frame() @property def length(self): """Property to get the length of the movie.""" if self._is_preloaded: return self._file.get_length() @property def has_video(self): """Property to check if movie has video.""" if self._is_preloaded: return self._file.has_video() @property def has_audio(self): """Property to check if movie has audio.""" if self._is_preloaded: return self._file.has_audio() def preload(self): """Preload stimulus to memory.""" if not self._is_preloaded: self._file = pygame.movie.Movie(unicode2str(self._filename, fse=True)) screen_size = expyriment._active_exp.screen.surface.get_size() self._pos = [screen_size[0] / 2 - self._file.get_size()[0] / 2 + self._position[0], screen_size[1] / 2 - self._file.get_size()[1] / 2 - self._position[1]] size = self._file.get_size() self._surface = pygame.surface.Surface(size) self._file.set_display(self._surface) self._is_preloaded = True def unload(self): """Unload stimulus from memory. This removes the reference to the object in memory. It is up to the garbage collector to actually remove it from memory. """ if self._is_preloaded: self._file = None self._surface = None self._is_preloaded = False def play(self): """Play the video stimulus from the current position.""" if mixer.get_init() is not None: message = "Mixer is still initialized, cannot play audio! Call \ expyriment.control.stop_audiosystem() before preloading the video." print "Warning: ", message if self._logging: expyriment._active_exp._event_file_log( "Video,warning," + message) if not self._is_preloaded: self.preload() if self._logging: expyriment._active_exp._event_file_log( "Video,playing,{0}".format(unicode2str(self._filename))) self._file.play() def stop(self): """Stop the video stimulus.""" if self._is_preloaded: self._file.stop() self.rewind() def pause(self): """Pause the video stimulus.""" if self._is_preloaded: self._file.pause() def forward(self, seconds): """Advance playback position. This will not forward immediately, but play a short period of the beginning of the file! This is a Pygame issue which we cannot fix right now. Parameters ---------- seconds : int amount to advance (in seconds) """ if self._is_preloaded: self._file.skip(float(seconds)) def rewind(self): """Rewind to start of video stimulus.""" if self._is_preloaded: self._file.rewind() self._frame = 0 def present(self): """Play the video and present current frame. This method starts video playback and presents a single frame (the current one). When using OpenGL, the method blocks until this frame is actually being written to the screen. """ self.play() while not self._file.get_frame() > self._frame: pass self.update() def update(self): """Update the screen on each new frame.""" if self._is_preloaded: frame = self._file.get_frame() if frame > self._frame: self._frame = frame if expyriment._active_exp._screen.open_gl: ogl_screen = _visual._LaminaPanelSurface( self._surface, position=self._position) ogl_screen.display() else: expyriment._active_exp._screen.surface.blit(self._surface, self._pos) expyriment._active_exp._screen.update() def _wait(self, frame=None): """Wait until frame was shown or end of movie and update screen. Parameters ---------- frame : int, optional number of the frame to stop after """ while self.is_playing: expyriment._active_exp._execute_wait_callback() old_frame = self._frame self.update() new_frame = self._frame if frame is not None and new_frame > frame: self.stop() break diff = new_frame - old_frame if diff > 1: warn_message = repr(diff - 1) + " video frames dropped!" print warn_message expyriment._active_exp._event_file_warn( "Video,warning," + warn_message) for event in pygame.event.get(pygame.KEYDOWN): if event.type == pygame.KEYDOWN and ( event.key == expyriment.control.defaults.quit_key or event.key == expyriment.control.defaults.pause_key): self.stop() Keyboard.process_control_keys(event) self.play() def wait_frame(self, frame): """Wait until certain frame was shown and constantly update screen. Notes ----- This function will also check for control keys (quit and pause). Thus, keyboard events will be cleared from the cue and cannot be received by a Keyboard().check() anymore! If keybaord events should not be cleared, a loop has to be created manually like:: movie.present() while movie.is_playing: movie.update() key = exp.keyboard.check() if key == ... Parameters ---------- frame : int number of the frame to stop after """ self._wait(frame) def wait_end(self, last_frame=None): """Wait until video has ended and constantly update screen. Notes ----- This will also check for control keys (quit and pause). Thus, keyboard events will be cleared from the cue and cannot be received by a Keyboard().check() anymore! If keybaord events should not be cleared, a loop has to be created manually like:: movie.present() while movie.is_playing: movie.update() key = exp.keyboard.check() if key == ... """ self._wait() python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_audio.py0000644000175000017500000001005212314561273024503 0ustar oliveroliver""" Audio playback. This module contains a class implementing audio playback. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os try: import android.mixer as mixer except: import pygame.mixer as mixer import expyriment from expyriment.misc import unicode2str from _stimulus import Stimulus class Audio(Stimulus): """A class implementing a general auditory stimulus. Notes ----- See also - expyriment.control.start_audiosystem - expyriment.control.stop_audiosystem - expyriment.control.audiosystem_is_busy - expyriment.control.audiosystem_wait_end """ def __init__(self, filename): """Create an audio stimulus. Parameters ---------- filename : str the filename """ Stimulus.__init__(self, filename) self._filename = filename self._file = None self._is_preloaded = False if not(os.path.isfile(self._filename)): raise IOError("The audio file {0} does not exists".format( unicode2str(self._filename))) _getter_exception_message = "Cannot set {0} if preloaded!" @property def is_preloaded(self): """Getter for is_preloaded""" return self._is_preloaded @property def filename(self): """Getter for filename""" return self._filename @filename.setter def filename(self, value): """Setter for filename.""" if self._is_preloaded: raise AttributeError(Audio._getter_exception_message.format( "filename")) else: self._filename = value def copy(self): """Copy the stimulus. Returns ------- copy: stimulus.Audio Returned copy will NOT be is_preloaded! """ was_loaded = self._is_preloaded self.unload() rtn = Stimulus.copy(self) if was_loaded: self.preload() return rtn def preload(self): """Preload stimulus to memory.""" if not self._is_preloaded: self._file = mixer.Sound(unicode2str(self._filename, fse=True)) self._is_preloaded = True def unload(self): """Unload stimulus from memory. This removes the reference to the object in memory. It is up to the garbage collector to actually remove it from memory. """ if self._is_preloaded: self._file = None self._is_preloaded = False def play(self, loops=0, maxtime=0, fade_ms=0): """Play the audio stimulus. The function returns immediately after the sound started to play. A pygame.mixer.Channel object is returned. Parameters ---------- loops : int, optional how often to repeat (-1 = forever) (default = 0) maxtime : int stop after given amount of milliseconds (default = 0) fade_ms : int, optional fade in time in milliseconds (default = 0) """ if not self._is_preloaded: self.preload() rtn = self._file.play(loops, maxtime, fade_ms) if self._logging: if isinstance(self._filename, unicode): import sys filename = self._filename.encode(sys.getfilesystemencoding()) else: filename = self._filename expyriment._active_exp._event_file_log( "Stimulus,played,{0}".format(filename), 1) return rtn def stop(self): """Stop the audio stimulus""" if self._is_preloaded: self._file.stop() def present(self): """Presents the sound. The function is identical to Audio.play(loops=0, maxtime=0, fade_ms=0) and returns also immediately after the sound started to play. Notes ----- See Audio.play for more information. """ self.play() python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/__init__.py0000644000175000017500000000153312314561273025006 0ustar oliveroliver"""The stimuli package. This Package contains a variety of classes implementing experimental stimuli. See also expyriment.stimuli.extras for more stimuli. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from _audio import Audio from _video import Video from _canvas import Canvas from _circle import Circle from _rectangle import Rectangle from _line import Line from _ellipse import Ellipse from _dot import Dot from _shape import Shape from _blankscreen import BlankScreen from _textline import TextLine from _fixcross import FixCross from _textbox import TextBox from _textscreen import TextScreen from _picture import Picture from _tone import Tone from _frame import Frame import extras python-expyriment-0.7.0+git34-g55a4e7e/expyriment/stimuli/_canvas.py0000644000175000017500000000456012314561273024664 0ustar oliveroliver#!/usr/bin/env python """ A Canvas stimulus. This module contains a class implementing a canvas stimulus. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame import defaults from _visual import Visual class Canvas(Visual): """A class implementing a canvas stimulus.""" def __init__(self, size, position=None, colour=None): """Create a canvas. Parameters ---------- size : (int, int) size of the canvas (int,int) position : (int, int), optional position of the stimulus colour : (int,int,int), optional colour of the canvas stimulus """ if position is None: position = defaults.canvas_position Visual.__init__(self, position) self._size = size if colour is not None: self._colour = colour else: self._colour = defaults.canvas_colour _getter_exception_message = "Cannot set {0} if surface exists!" @property def size(self): """Getter for size.""" return self._size @size.setter def size(self, value): """Setter for size.""" if self.has__surface: raise AttributeError(Canvas._getter_exception_message.format( "size")) else: self._size = value @property def colour(self): """Getter for colour.""" return self._colour @colour.setter def colour(self, value): """Setter for colour.""" if self.has__surface: raise AttributeError(Canvas._getter_exception_message.format( "colour")) else: self._colour = value def _create_surface(self): """Create the surface of the stimulus.""" surface = pygame.surface.Surface(self._size, pygame.SRCALPHA).convert_alpha() if self._colour is not None: surface.fill(self._colour) return surface if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() cnvs = Canvas((200, 200), colour=(255, 255, 255)) cnvs.present() exp.clock.wait(1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/0000775000175000017500000000000012314561273022460 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/extras/0000775000175000017500000000000012314561273023766 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/extras/defaults.py0000644000175000017500000000113612314561273026146 0ustar oliveroliver""" Default settings for design.extras. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugin_defaults(__file__), _importer_functions.import_plugin_defaults_from_home(__file__)]: for _defaults in _plugins: exec(_defaults) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/extras/__init__.py0000644000175000017500000000123512314561273026076 0ustar oliveroliver"""The design extra package. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os import defaults from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugins(__file__), _importer_functions.import_plugins_from_settings_folder(__file__)]: for _plugin in _plugins: try: exec(_plugins[_plugin]) except: print("Warning: Could not import {0}".format( _os.path.dirname(__file__) + _os.sep + _plugin)) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/randomize.py0000644000175000017500000000426012314561273025022 0ustar oliveroliver"""The expyriment randomize module. This module contains various functions for randomizing data """ __author__ = 'Florian Krause ,\ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from copy import copy as _copy import random as _random _random.seed() def rand_int_sequence(first_elem, last_elem): """Return a randomised sequence of integers in given range. Parameters ---------- first_elem : int first element of the range last_elem : int last element of the range Results ------- rnd_seq : list randomised sequence of integers in given range """ list_ = range(first_elem, last_elem + 1) _random.shuffle(list_) return list_ def rand_int(a, b): """Return random integer in given range. Parameters ---------- a : int first element of range b : int last element of range Results ------- rnd : int """ return _random.randint(a, b) def rand_element(list_): """Return a random element from a list Parameter --------- list_ : list Results ------- elem : a random element from the list """ return list_[_random.randint(0, len(list_) - 1)] def coin_flip(): """Return randomly True or False. Returns ------- rnd : bool """ if _random.randint(1, 2) == 1: return True else: return False def shuffle_list(list_): """Shuffle any list of objects. Parameters ---------- list_ : int list to shuffle """ _random.shuffle(list_) def make_multiplied_shuffled_list(list_, xtimes): """Return the multiplied and shuffled (sectionwise) list. The function manifolds the list 'xtimes' and shuffles each and concatenates to the return new lists. Parameters ---------- list_ : list list to be shuffled xtimes : int how often the list will be multiplied """ newlist = [] tmp = _copy(list_) for _i in range(0, xtimes): _random.shuffle(tmp) newlist.extend(tmp) return newlist python-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/defaults.py0000644000175000017500000000177112314561273024645 0ustar oliveroliver""" Default settings for the design package. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os from expyriment.misc import str2unicode as _str2unicode # Experiment experiment_name = None # Set None if experiment default name should be the # name of the python main file experiment_background_colour = (0, 0, 0) experiment_foreground_colour = (150, 150, 150) #experiment_text_font = _str2unicode(_os.path.abspath( # _os.path.join(_os.path.dirname(__file__), # "..", "_fonts", "FreeSans.ttf"))) experiment_text_font = "FreeSans" experiment_text_size = 20 experiment_filename_suffix = None # Block block_name = 'unnamed' max_shuffle_time = 5000 # trial_list trial_list_directory = 'trials' python-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/_structure.py0000644000175000017500000015344412314561273025242 0ustar oliveroliver""" The design._structure module of expyriment. This module contains a class implementing the experiment structure. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os try: import locale except ImportError: locale = None # Does not exist on Android import sys import types import codecs import re try: import csv except ImportError: from expyriment.misc import _csv_reader_android as csv from copy import deepcopy import defaults import expyriment from expyriment.misc import constants from expyriment.misc import Clock from expyriment.misc import unicode2str, str2unicode import randomize import permute class Experiment(object): """A class implementing a basic experiment.""" def __init__(self, name=None, foreground_colour=None, background_colour=None, text_font=None, text_size=None, filename_suffix=None): """Create an experiment. Parameters ---------- name : str, optional name of the experiment foreground_colour : (int, int, int), optional background_colour : (int,int,int), optional text_font : str, optional text_size : int, optional filename_suffix : str, optional additional suffix that will be added to the main event and data filename """ if name is not None: self._name = name elif defaults.experiment_name is not None: self._name = defaults.experiment_name else: self._name = os.path.split(sys.argv[0])[1].replace(".py", "") if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ defaults.experiment_background_colour if foreground_colour is not None: self._foreground_colour = foreground_colour else: self._foreground_colour = \ defaults.experiment_foreground_colour if text_font is not None: self._text_font = text_font else: self._text_font = defaults.experiment_text_font if text_size is not None: self._text_size = text_size else: self._text_size = defaults.experiment_text_size if filename_suffix is not None: self._filename_suffix = filename_suffix else: self._filename_suffix = defaults.experiment_filename_suffix self.clear_bws_factors() self._data_variable_names = [] self._experiment_info = [] self._blocks = [] self._block_id_counter = 0 self._is_started = False self._is_initialized = False self._keyboard = None self._mouse = None self._clock = None self._subject = None self._screen = None self._data = None self._events = None self._log_level = 0 # will be set from initialize self._wait_callback_function = None @property def name(self): """Getter for name.""" return self._name @property def blocks(self): """Getter for blocks.""" return self._blocks @property def background_colour(self): """Getter for background_colour.""" return self._background_colour @property def foreground_colour(self): """Getter for foreground_colour.""" return self._foreground_colour @property def text_font(self): """Getter for text_font.""" return self._text_font @property def text_size(self): """Getter for text_size.""" return self._text_size @property def filename_suffix(self): """Getter for filename suffix.""" return self._filename_suffix @property def screen(self): """Getter for global screen.""" return self._screen @property def data(self): """Getter for main data file.""" return self._data @property def events(self): """Getter for main event files.""" return self._events @property def log_level(self): """Getter for event logging. """ return self._log_level @property def clock(self): """Getter for global clock.""" return self._clock @property def keyboard(self): """Getter for global keyboard.""" return self._keyboard @property def mouse(self): """Getter for global keyboard.""" return self._mouse @property def subject(self): """Getter for global subject id.""" return self._subject @property def is_started(self): """Getter for is_started.""" return self._is_started @property def is_initialized(self): """Getter for is_initialized.""" return self._is_initialized def __str__(self): tmp_str = "Experiment: {0}\n".format(unicode2str(self.name)) if len(self.bws_factor_names) <= 0: tmp_str = tmp_str + "no between subject factors\n" else: tmp_str = tmp_str + "between subject factors (permutation type: " if self.bws_factor_randomized: tmp_str = tmp_str + "random)\n" else: tmp_str = tmp_str + "latin square)\n" for f in self.bws_factor_names: _bws_factor = \ [unicode2str(x) if isinstance(x, unicode) else repr(x) for x in self._bws_factors[f]] tmp_str = tmp_str + " {0} = [{1}]\n".format( unicode2str(f), ", ".join(_bws_factor)) for block in self.blocks: tmp_str = tmp_str + "{0}\n".format(unicode2str(block.summary)) return tmp_str @property def data_variable_names(self): """Getter for data_variable_names.""" return self._data_variable_names @data_variable_names.setter def data_variable_names(self, value): """Setter for data_variable_names.""" self.clear_data_variable_names() self.add_data_variable_names(value) def clear_data_variable_names(self): """Remove all data variable names from design.""" self._data_variable_names = [] if self.data is not None: self.data.clear_variable_names() def add_data_variable_names(self, variable_names): """Add data variable names to the design. Parameters ---------- variables : str or lst of str variable names """ if variable_names is None: return if type(variable_names) is not list: variable_names = [variable_names] self._data_variable_names.extend(variable_names) if self.data is not None: self.data.add_variable_names(variable_names) @property def experiment_info(self): # experiment_info can not be cleared! """Getter for experiment_info.""" return self._experiment_info def add_experiment_info(self, text): """Add experiment information to the design. Parameters ---------- text : string or list of str text lines to be added as experiment information """ if text is None: return if type(text) is not list: text = [text] self._experiment_info.extend(text) if self.data is not None: self.data.add_experiment_info(text) @property def bws_factor_randomized(self): """Getter for bws_factor_randomized. Notes ----- Is between subject factor randomized? (True/False). If True conditions will be assigned randomized otherwise (default) conditions will be systematically permuted across subjects. """ return self._bws_factor_randomized @bws_factor_randomized.setter def bws_factor_randomized(self, value): """Setter for bws_factor_randomized.""" self._bws_factor_randomized = value def add_bws_factor(self, factor_name, conditions): """Add a between subject factor. The defined between subject factor conditions will be permuted across the subjects. Factors that are added first are treated as hierarchically higher factors while permutation. Parameters ---------- factor_name : str the name of the between subject factor conditions : list possible conditions of the between subject factor """ if type(conditions) is not list: conditions = [conditions] self._bws_factors[factor_name] = conditions self._bws_factors_names.append(factor_name) self._randomized_condition_for_subject[factor_name] = {} def get_bws_factor(self, factor_name): """Return all conditions of this between subject factor.""" try: cond = self._bws_factors[factor_name] except: return None return cond def get_permuted_bws_factor_condition(self, factor_name, subject_id=None): """Get the between subject factor condition for a subject. The condition for the current subject will be returned, if expyriment is running and the function is called without a subject_id. If the function is called with a subject_id, the condition for this particular subject will be returned. Parameters ---------- factor_name : str subject_id : str, optional (default=None) Returns ------- cond : str condition for the current subject """ if subject_id is None: if self.subject is None: # No current subject id defined return None else: # Use current subject return self.get_permuted_bws_factor_condition( factor_name, subject_id=self.subject) else: conditions = self.get_bws_factor(factor_name) if conditions is None: return None # Factor not defined else: cond_idx = 0 if self.bws_factor_randomized: try: cond_idx = self._randomized_condition_for_subject[ factor_name][subject_id] except: # If not yet randomized for this subject, do it cond_idx = randomize.rand_int( 0, len(self._bws_factors[factor_name]) - 1) self._randomized_condition_for_subject[ factor_name][subject_id] = cond_idx else: # Permutation # (n_cond_lower_fac) total number of conditions for all # hierarchically lower factors n_cond_lower_fac = self.n_bws_factor_conditions for fac in self.bws_factor_names: n_cond_lower_fac -= len(self.get_bws_factor(fac)) if fac is factor_name: break if n_cond_lower_fac <= 0: n_cond_lower_fac = 1 cond_idx = ((subject_id - 1) / n_cond_lower_fac) % \ len(self.get_bws_factor(fac)) return self._bws_factors[factor_name][cond_idx] def clear_bws_factors(self): """Remove all between subject factors from design.""" self._bws_factors = {} # Can't use dict_keys, because dicts don't keep the order self._bws_factors_names = [] self._randomized_condition_for_subject = {} self.bws_factor_randomized = False @property def bws_factor_names(self): """Getter for factors keys.""" return self._bws_factors_names @property def n_bws_factor_conditions(self): """Getter for n_bws_factor_conditions. Total number of conditions in all bws_factors. """ n = 0 for fn in self.bws_factor_names: n += len(self.get_bws_factor(fn)) return n def set_log_level(self, loglevel): """Set the log level of the current experiment Parameters ---------- loglevel : int The log level (0, 1, 2) of the experiment. Notes ----- There are three event logging levels: - O no event logging - 1 normal event logging (logging of all input & output events) - 2 intensive logging. Logs much more. Please use this only for debugging proposes. In most cases, it should be avoided to switch of logging (loglevel=0). It log files become to big due to certain repetitive events, it is suggested to switch of the logging of individual stimuli or IO event. (see the method `.set_logging()` of this objects) The logging of events can be also changed before initialize via the default value `expyriment.control.defaults.event_logging`. """ self._log_level = int(loglevel) def add_block(self, block, copies=1): """Add a block to the experiment. Parameters ---------- block : design.Block block to add copies : int, optional number of copies to add (default = 1) """ for _x in range(0, copies): self._blocks.append(block.copy()) self._blocks[-1]._id = self._block_id_counter self._block_id_counter += 1 expyriment._active_exp._event_file_log( "Experiment,block added,{0},{1}".format( unicode2str(self.name), self._blocks[-1]._id), 2) def remove_block(self, position): """Remove block from experiment. If no position is given, the last one is removed. Parameters ---------- position : int position of the block to be removed """ block = self._blocks.pop(position) expyriment._active_exp._event_file_log( "Experiment,block removed,{0},{1}".format(unicode2str(self.name), block.id), 2) def clear_blocks(self): """Remove all blocks from experiment.""" self._blocks = [] self._block_id_counter = 0 expyriment._active_exp._event_file_log("Experiment,blocks cleared", 2) def order_blocks(self, order): """Order the blocks. Parameters ---------- order : list list with the new order of positions """ if not len(order) == len(self._blocks): raise ValueError("Given order has wrong number of items!") blocks_new = [] for position in order: blocks_new.append(self._blocks[position]) self._blocks = blocks_new @property def n_blocks(self): """Getter for n_blocks. Number of blocks. """ return len(self._blocks) def swap_blocks(self, position1, position2): """Swap two blocks. Parameters ---------- position1 : int position of first block position2 : int position of second block """ if position1 < len(self._blocks) and position2 < len(self._blocks): self._blocks[position1], self._blocks[position2] = \ self._blocks[position2], self._blocks[position1] return True else: return False def shuffle_blocks(self): """Shuffle all blocks.""" randomize.shuffle_list(self._blocks) def permute_blocks(self, permutation_type, factor_names=None, subject_id=None): """Permute the blocks. Parameters ---------- permutation_type : int (permutation type) type of block order permutation (permutation type) Permutation types defined in misc.constants: P_BALANCED_LATIN_SQUARE, P_CYCLED_LATIN_SQUARE, and P_RANDOM factor_names : list (of strings), optional list of the factor names to be considered while permutation. If factor_names are not defined (None) all factors will be used. subject_id : int, optional subject number for this permutation If subject_id is defined or none (default) and experiment has been started, the current subject number will be used """ if subject_id is None: if self.subject is None: raise RuntimeError("If Expyriment is not started, \ a subject number needs to be defined for the permutation.") else: subject_id = self.subject if not permute.is_permutation_type(permutation_type): raise AttributeError("{0} is a unknown permutation \ type".format(permutation_type)) if factor_names is None: factor_names = self.block_list_factor_names # Get the condition combinations for the specified factors: all_factor_combi = [] for b in self.blocks: combi = [] for f in factor_names: combi.append([f, b.get_factor(f)]) new = True for c in all_factor_combi: if c == combi: new = False if new: # Add only a new combination all_factor_combi.append(combi) # Get the permutation if permutation_type == constants.P_BALANCED_LATIN_SQUARE: permutation = permute.balanced_latin_square(all_factor_combi) idx = (subject_id - 1) % len(permutation) permutation = permutation[idx] elif permutation_type == constants.P_CYCLED_LATIN_SQUARE: permutation = permute.cycled_latin_square(all_factor_combi) idx = (subject_id - 1) % len(permutation) permutation = permutation[idx] else: randomize.shuffle_list(all_factor_combi) permutation = all_factor_combi tmp = self._blocks self._blocks = [] for search_combi in permutation: # Search tmp block for this comb # And add all fitting blocks (multiple addings possible) for b in tmp: combi = [] for f in factor_names: combi.append([f, b.get_factor(f)]) if combi == search_combi: self._blocks.append(b) def sort_blocks(self): """Sort the blocks according to their indices from low to high.""" blocks_new = [] id_list = [x.id for x in self._blocks] id_list.sort() for id in id_list: position = [i for i, x in enumerate(self._blocks) if x.id == id][0] blocks_new.append(self._blocks[position]) self._blocks = blocks_new def find_block(self, id): """Find the position of a block, given the id. Parameters ---------- id : int block id to look for Returns ------- pos: int positions as a list or None if not in block list. """ positions = [i for i, x in enumerate(self._blocks) if x.id == id] if positions: return positions @property def trial_factor_names(self): """Getter for trial_factor_nanes. Get all factor names defined in the trial lists of all blocks. """ factors = [] for bl in self.blocks: factors.extend(bl.trial_factor_names) return list(set(factors)) @property def block_list_factor_names(self): """Getter for block_list_factor_names. Get all factor names defined in all blocks. """ factors = [] for bl in self.blocks: factors.extend(bl.factor_names) return list(set(factors)) @property def design_as_text(self): """Getter for desing_as_text. Trial list as csv table. """ rtn = u"#exp: {0}\n".format(self.name) if len(self.experiment_info) > 0: for txt in self.experiment_info: rtn += u"#xpi: {0}\n".format(txt) if len(self.bws_factor_names) > 0: for factor_name in self.bws_factor_names: rtn += u"#bws: {0}=".format(factor_name) for txt in self.get_bws_factor(factor_name): rtn += u"{0},".format(txt) rtn = rtn[:-1] + "\n" # delete last comma rtn += u"#bws-rand: {0}\n".format(int(self.bws_factor_randomized)) if len(self.data_variable_names) > 0: rtn += "#dvn: " for txt in self.data_variable_names: rtn += u"{0},".format(txt) rtn = rtn[:-1] + "\n" rtn += "block_cnt,block_id" bl_factors = self.block_list_factor_names factors = self.trial_factor_names for f in bl_factors: rtn += u",block_{0}".format(f) rtn += ",trial_cnt,trial_id" for f in factors: rtn += u",{0}".format(f) for bl_cnt, bl in enumerate(self.blocks): for tr_cnt, tr in enumerate(bl.trials): rtn += u"\n{0},{1}".format(bl_cnt, bl.id) for f in bl_factors: rtn += u",{0}".format(bl.get_factor(f)) rtn += u",{0},{1}".format(tr_cnt, tr.id) for f in factors: rtn += u",{0}".format(tr.get_factor(f)) return rtn def save_design(self, filename): """Save the design as list of trials to a csv file. The function considers only the defined trial factors and not the added stimuli. Notes ----- The current version of this function does not handle between_subject factors and data_variables. Parameters ---------- filename : str name (fullpath) of the csv file (str) """ with open(filename, 'w') as f: try: locale_enc = locale.getdefaultlocale()[1] except: locale_enc = "UTF-8" header = "# -*- coding: {0} -*-\n".format( locale_enc) f.write(header + unicode2str(self.design_as_text)) def load_design(self, filename, encoding=None): """Load the design from a csv file containing list of trials. The function considers only the defined trial factors and not the added stimuli. The existing design will be deleted. Notes ----- The current version of this function does not handle between_subject factors and data_variables. Parameters ---------- filename : str name (fullpath) of the csv file (str) encoding : str, optional the encoding to be used when reading from the file """ delimiter = "," self.clear_blocks() block_factors = {} trial_factors = {} if encoding is None: with open(filename, 'r') as fl: first_line = fl.readline() encoding = re.findall("coding[:=]\s*([-\w.]+)", first_line) if encoding == []: second_line = fl.readline() encoding = re.findall("coding[:=]\s*([-\w.]+)", second_line) if encoding == []: encoding = [None] else: encoding = [encoding] with codecs.open(filename, 'rb', encoding[0], errors='replace') as fl: for ln in fl: ln = str2unicode(ln) if ln[0] == "#": if ln.startswith("#exp:"): self._name = ln[6:].strip() elif ln.startswith("#xpi:"): self.add_experiment_info(ln[6:].strip()) elif ln.startswith("#dvn:"): for tmp in ln[6:].split(","): self.add_data_variable_names(tmp.strip()) elif ln.startswith("#bws-rand:"): self.bws_factor_randomized = (ln[11] == "1") elif ln.startswith("#bws:"): tmp = ln[6:].split("=") print tmp[1].strip().split(",") self.add_bws_factor(tmp[0], tmp[1].strip().split(",")) else: # data line if len(block_factors) < 1: # read first no-comment line --> varnames for col, var in enumerate(ln.split(delimiter)): var = var.strip() if var.startswith("block_"): var = var.replace("block_", "") block_factors[col] = var elif var.startswith("trial_"): var = var.replace("trial_", "") trial_factors[col] = var else: trial_factors[col] = var if not("cnt" in block_factors.values() and "id" in block_factors.values() and "cnt" in trial_factors.values() and "id" in trial_factors.values()): message = "Can't read design file. " + \ "The file '{0}' ".format( unicode2str(filename)) + \ "does not contain an Expyriment trial list." raise IOError(message) else: block_cnt = None trial_cnt = None # read data for col, val in enumerate(ln.split(delimiter)): val = val.strip() # try to convert to number if val.find(".") >= 0: try: val = float(val) except: pass else: try: val = int(val) except: pass # set value to block or trial if col in block_factors: if block_factors[col] == "cnt": block_cnt = val while len(self.blocks) < block_cnt + 1: self.add_block(Block()) elif block_factors[col] == "id": self.blocks[block_cnt]._id = val else: self.blocks[block_cnt].set_factor( block_factors[col], val) if col in trial_factors: if trial_factors[col] == "cnt": trial_cnt = val while len(self.blocks[block_cnt].trials)\ < trial_cnt + 1: self.blocks[block_cnt].add_trial( Trial()) elif trial_factors[col] == "id": self.blocks[block_cnt].trials[trial_cnt].\ _id = val else: self.blocks[block_cnt].trials[trial_cnt].\ set_factor(trial_factors[col], val) def _event_file_log(self, log_text, log_level=1): # log_level 1 = default, 2 = extensive, 0 or False = off """ Helper function to log event in the global experiment event file""" if self.is_initialized and\ self._log_level > 0 and\ self._log_level >= log_level and \ self.events is not None: self.events.log(log_text) def _event_file_warn(self, warning, log_level=1): """ Helper function to log event in the global experiment event file""" if self.is_initialized and\ self._log_level > 0 and\ self._log_level >= log_level and \ self.events is not None: self.events.log(warning) def log_design_to_event_file(self, additional_comment=""): """Log the design (as comment) to the current main event file. If no experiment is initialized or no event file exists the function will not do anything. This function will be automatically called after an experiment has been started. Notes ----- See also save_design(). Parameters ---------- additional_comment : str, optional additional comment that will be logged """ if self.is_initialized and self.events is not None: self.events.log("design,log,{0}".format( unicode2str(additional_comment))) for ln in self.design_as_text.splitlines(): self.events.write_comment( "design: {0}".format(unicode2str(ln)).replace( ":#", "-")) self.events.log("design,logged,{0}".format( unicode2str(additional_comment))) def register_wait_callback_function(self, function): """Register a wait callback function. Notes ----- CAUTION! If wait callback function takes longer than 1 ms to process, Expyriment timing will be affected! The registered wait callback function will be repetitively executed in all Expyriment wait and event loops that wait for an external input. That is, they are executed by the following functions (at least once!): - control.wait_end_audiosystem - misc.clock.wait - misc.clock.wait_seconds - misc.clock.wait_minutes - io.keyboard.wait - io.keyboard.wait_char - io.buttonbox.wait - io.gamepad.wait_press - io.triggerinput.wait - io.mouse.wait_press - io.serialport.read_line - io.textinput.get - io.TouchScreenButtonBox.wait - io.extras.CedrusResponseDevice.wait - stimulus.video.wait_frame - stimulus.video.wait_end Parameters ---------- function : function wait function (function) """ if type(function) == types.FunctionType: self._wait_callback_function = function else: raise AttributeError("register_wait_callback_function requires " + "a function as parameter") def unregister_wait_callback_function(self): """Unregister wait function.""" self._wait_callback_function = None def _execute_wait_callback(self): """Execute wait function. Returns True if wait function is defined and executed. """ if self._wait_callback_function is not None: self._wait_callback_function() return True else: return False class Block(object): """A class implementing an experimental block.""" _trial_cnt_variable_name = "trial_cnt" # variable names for csv in/output _trial_id_variable_name = "trial_id" def __init__(self, name=None): """Create a block. Parameters ---------- name : str, optional name of the block """ if name is not None: self._name = name else: self._name = defaults.block_name self._factors = {} self._trials = [] self._trial_id_counter = 0 self._id = None @property def name(self): """Getter for name.""" return self._name @property def id(self): """Getter for id.""" return self._id @property def trials(self): """Getter for trials.""" return self._trials def __str__(self): return unicode2str(self._get_summary(True)) @property def summary(self): """Getter for summary.""" return self._get_summary(False) def _get_summary(self, include_trial_IDs): """Return a summary of the trials as string.""" if self.name is None: name = "" else: name = self.name rtn = u"""Block {0}: {1} block factors: {2} n trials: {3}""".format(self.id, name, self.factors_as_text, len(self.trials)) if include_trial_IDs: rtn = rtn + u""" trial IDs = {0}""".format([t.id for t in self.trials]) rtn = rtn + u""" trial factors: """ for f in self.trial_factor_names: val = [] for tf in self.get_trial_factor_values(f): if tf not in val: val.append(tf) val.sort() val = [repr(x) if type(x) not in [unicode, str] else x for x in val] rtn = rtn + u"{0} = [{1}]\n ".format( f, ", ".join(val)) return rtn @property def factors_as_text(self): """Getter for factors_as_text. Return all factor names and values as string line. """ all_factors = "" for f in self.factor_names: all_factors = all_factors + \ u"{0} = {1}\n ".format( f, self.get_factor(f)) all_factors = all_factors.rstrip() if len(all_factors) >= 1 and all_factors[-1] == ",": all_factors = all_factors[:-1] return all_factors def set_factor(self, name, value): """Set a factor for the block. Parameters ---------- name : str factor name value : str or numeric factor value """ if type(value) in [types.StringType, types.UnicodeType, types.IntType, types.FloatType]: self._factors[name] = value else: message = "Factor values or factor conditions must to be a " + \ "String or a Number (i.e. float or integer).\n " + \ "{0} is not allowed.".format(type(value)) raise TypeError(message) def get_factor(self, name): """Get a factor of the block. Parameters ---------- name : str factor name (str) """ try: rtn = self._factors[name] except: rtn = None return rtn @property def factor_dict(self): """The dictionary with all factors of the block.""" return self._factors def clear_factors(self): """Clear all factors.""" self._factors = {} @property def factor_names(self): """Getter for factor_names. Factor keys. """ return self._factors.keys() def get_random_trial(self): """Returns a randomly selected trial. Notes ----- This function is useful for compiling training blocks. Returns ------- rnd : design.Trial random Expyriment trial """ rnd = randomize.rand_int(0, len(self._trials) - 1) return self._trials[rnd] def add_trial(self, trial, copies=1, random_position=False): """Add trial to the block. Parameters ---------- trial : design.Trial trial to add copies : int, optional number of copies to add (default = 1) random_position : bool, optional True = insert trials at random position, False = append trials at the end (default=False) """ for _x in range(0, copies): if random_position: pos = randomize.rand_int(0, len(self._trials)) self._trials.insert(pos, trial.copy()) else: self._trials.append(trial.copy()) self._trials[-1]._id = self._trial_id_counter self._trial_id_counter += 1 log_txt = "Block,trial added,{0}, {1}".format(unicode2str(self.name), self._trials[-1]._id) if random_position: log_txt = log_txt + ", random position" expyriment._active_exp._event_file_log(log_txt, 2) def remove_trial(self, position): """Remove a trial. Parameters ---------- position : int position of the trial """ trial = self._trials.pop(position) expyriment._active_exp._event_file_log( "Block,trial removed,{0},{1}".format(self.id, trial.id), 2) def clear_trials(self): """Clear all trials.""" self._trials = [] self._trial_id_counter = 0 expyriment._active_exp._event_file_log("Block,trials cleared", 2) @property def n_trials(self): """Getter for n_trials. Number of trials. """ return len(self._trials) @property def trial_factor_names(self): """Getter for trial_factor_names. Get all factor names defined in trial list. """ if len(self.trials) < 1: return [] rtn = self.trials[0].factor_names for tr in self.trials: for new_fac in tr.factor_names: is_new = True for old_fac in rtn: if old_fac == new_fac: is_new = False break if is_new: rtn.append(new_fac) return rtn def get_trial_factor_values(self, name): """Return a list of the values of a certain factor for all trials. Parameters ---------- name : str name of the factor """ rtn = [] for trial in self.trials: rtn.append(trial.get_factor(name)) return rtn @property def design_as_text(self): """Getter for design_as_text. List of trial factors as csv table. The list considers only the defined trial factors and not the added stimuli. """ rtn = "{0},{1}".format(self._trial_cnt_variable_name, self._trial_id_variable_name) factors = self.trial_factor_names for f in factors: rtn = rtn + ",{0}".format(unicode2str(f)) for cnt, tr in enumerate(self.trials): rtn = rtn + "\n{0},{1}".format(cnt, tr.id) for f in factors: rtn = rtn + ",{0}".format(unicode2str(tr.get_factor(f))) return rtn def save_design(self, filename): """Save the list of trials to a csv file. The function considers only the defined trial factors and not the added stimuli. Parameters filename -- name (fullpath) of the csv file """ with open(filename, 'w') as f: try: locale_enc = locale.getdefaultlocale()[1] except: locale_enc = "UTF-8" header = "# -*- coding: {0} -*-\n".format( locale_enc) f.write(header + unicode2str(self.design_as_text)) def read_design(self, filename): """Reads a list of trials from a csv file and clears the old block design. The function considers only the trial factors and not the added stimuli. All factors will be read in as text strings and not casted to numericals. Please do this manually if required. Parameters ---------- filename : str name (fullpath) of the csv file (str) """ tmp = Block() tmp.add_trials_from_csv_file(filename) fac_names = tmp.trial_factor_names self.clear_factors() self.clear_trials() for tr in tmp.trials: new = Trial() for fac in fac_names: if fac == self._trial_cnt_variable_name: pass elif fac == self._trial_id_variable_name: new._id = int(tr.get_factor(fac)) else: new.set_factor(fac, tr.get_factor(fac)) self.add_trial(new) def add_trials_from_csv_file(self, filename, encoding=None): """Read a list of trials from csv-file and append the new trials to the block. Trials are defined as combinations of trial factors. **csv-file specifications** The first row of the csv-file specifies the factor names. Each following row describes one trial. Each row must have the same amount of columns. Notes ------ All factors will be read in as text strings and not casted to numericals. Please do this manually if required. Parameters ---------- filename : str name (fullpath) of the csv file (str) encoding : str, optional the encoding to be used when reading from the file """ factor_names = [] if encoding is None: with open(filename, 'r') as fl: first_line = fl.readline() encoding = re.findall("coding[:=]\s*([-\w.]+)", first_line) if encoding == []: second_line = fl.readline() encoding = re.findall("coding[:=]\s*([-\w.]+)", second_line) if encoding == []: encoding = [None] else: encoding = [encoding] with codecs.open(filename, "rb", encoding[0], errors='replace') as f: reader = csv.reader(f) for r_cnt, row in enumerate(reader): if r_cnt == 0: factor_names = deepcopy(str2unicode(row)) else: trial = Trial() for c_cnt in range(0, len(row)): trial.set_factor(str2unicode(factor_names[c_cnt]), str2unicode(row[c_cnt])) self.add_trial(trial) def order_trials(self, order): """Order the trials. Parameters ---------- order : list list with the new order of positions """ if not len(order) == len(self._trials): raise ValueError("Given order has wrong number of items!") trials_new = [] for position in order: trials_new.append(self._trials[position]) self._trials = trials_new def swap_trials(self, position1, position2): """Swap two trials. Parameters ---------- position1 : int position of first trial position2 : int position of second trial """ if position1 < len(self._trials) and position2 < len(self._trials): self._trials[position1], self._trials[position2] = \ self._trials[position2], self._trials[position1] return True else: return False @property def max_trial_repetitions(self): """Getter for max_trial_repetitions. Returns the maximum number of immediate trial repetitions. """ tmp = [] for t in self._trials: tmp.append(t.factors_as_text) max_reps = 0 cnt = 0 for x in range(1, len(tmp) - 1): if tmp[x - 1] == tmp[x]: cnt += 1 if cnt > max_reps: max_reps = cnt else: cnt = 0 return max_reps def shuffle_trials(self, method=0, max_repetitions=None): """Shuffle all trials. The function returns False if no randomization could be found that fulfills the max immediate trial repetition criterion. The different type of trials are only defined by the factors. Shuffle does not take into account the added stimuli. The following randomization methods are defined: 0 = total randomization of trial order (default), 1 = randomization within small miniblocks. Each miniblock contains one trial of each type (only defined by factors!). In other words, copies of one trial type are always in different miniblocks. Parameters ---------- method : int, optional method of trial randomization (default=0) max_repetitions : int, optional maximum number of allowed immediate repetitions. If None the repetition criterion will be ignored Returns ------- succeeded : bool """ start = Clock._cpu_time() cnt = 0 while True: cnt += 1 self._shuffle_trials(method) if max_repetitions is None or \ (self.max_trial_repetitions <= max_repetitions): return True else: if (Clock._cpu_time() - start) * 1000 >= \ defaults.max_shuffle_time: print "Warning: Could not find an appropriate trial " + \ "randomization ({0} attempts)!".format(cnt) return False def _shuffle_trials(self, method): """actual implementation of the trial shuffling""" if method == 1: # shuffling blockwise cp = self.copy() randomize.shuffle_list(cp._trials) self._trials = [] types_occured = [] cnt = 0 while len(cp._trials) > 0: tr = cp._trials[cnt] new = True tr_type = tr.factors_as_text for occ in types_occured: if tr_type == occ: new = False break if new: self._trials.append(tr) cp._trials.pop(cnt) types_occured.append(tr_type) cnt = 0 else: cnt = cnt + 1 if cnt >= len(cp._trials): types_occured = [] cnt = 0 else: randomize.shuffle_list(self._trials) def sort_trials(self): """Sort the trials according to their indices from low to high.""" trials_new = [] id_list = [x.id for x in self._trials] id_list.sort() for _id in id_list: position = [i for i, x in enumerate(self._trials) if x.id == _id][0] trials_new.append(self._trials[position]) self._trials = trials_new def find_trial(self, id): """Find the positions of a trial. Parameters ----------- id : int trial id to look for Returns ------- pos : list positions as a list or None if not in trial list. """ positions = [i for i, x in enumerate(self._trials) if x.id == id] if positions: return positions def copy(self): """Return a copy of the block.""" owntrials = [] triallist = [] for trial in self._trials: owntrials.append(trial) triallist.append(trial.copy()) self._trials = None rtn = deepcopy(self) self._trials = owntrials rtn._trials = triallist return rtn class Trial(object): """A class implementing an experimental trial.""" def __init__(self): """Create a Trial.""" self._stimuli = [] self._factors = {} self._id = None @property def stimuli(self): """Getter for stimuli.""" return self._stimuli @property def id(self): """Getter for id.""" return self._id def __str__(self): return """ Trial: {0} Stimuli: {1} """.format(str(self.id), [stimulus.id for stimulus in self.stimuli]) def set_factor(self, name, value): """Set a factor for the trial. Parameters ---------- name : str factor name value : str or numeric factor value """ if type(value) in [types.StringType, types.UnicodeType, types.IntType, types.LongType, types.FloatType]: self._factors[name] = value else: message = "Factor values or factor conditions must to be a " + \ "string or a numeric (i.e. float or integer).\n " + \ "{0} is not allowed.".format(type(value)) raise TypeError(message) def get_factor(self, name): """Get a factor of the trial. Parameters ---------- name : str factor name Returns ------- factor_val : str or numeric """ try: rtn = self._factors[name] except: rtn = None return rtn @property def factor_dict(self): """The dictionary with all factors of the trial.""" return self._factors def clear_factors(self): """Clear all factors.""" self._factors = {} @property def factor_names(self): """Getter for factors names.""" return self._factors.keys() @property def factors_as_text(self): """Return all factor names and values as csv string line""" all_factors = "" for f in self.factor_names: all_factors = all_factors + "{0}={1}, ".format( unicode2str(f), unicode2str(self.get_factor(f))) return all_factors def add_stimulus(self, stimulus): """Add a stimulus to the trial. Notes ----- This will add references to stimuli, not copies! Parameters ---------- stimulus : expyriment stimulus stimulus to add (expyriment.stimuli.* object) """ self._stimuli.append(stimulus) expyriment._active_exp._event_file_log( "Trial,stimulus added,{0},{1}".format(self.id, stimulus.id), 2) def remove_stimulus(self, position): """Remove stimulus from trial. Parameters ---------- position : int position of the stimulus """ stimulus = self._stimuli.pop(position) expyriment._active_exp._event_file_log( "Trial,stimulus removed,{0},{1}".format(self.id, stimulus.id), 2) def order_stimuli(self, order): """Order the stimuli. Parameters ---------- order : list list with the new order of positions """ if not len(order) == len(self._stimuli): raise ValueError("Given order has wrong number of items!") stimuli_new = [] for position in order: stimuli_new.append(self._stimuli[position]) self._stimuli = stimuli_new def clear_stimuli(self): """Clear the stimuli.""" self._stimuli = [] expyriment._active_exp._event_file_log("Trial,stimuli cleared", 2) def swap_stimuli(self, position1, position2): """Swap two stimuli. Parameters ---------- position1 : int position of first stimulus position2 : int position of second stimulus """ if position1 < len(self._stimuli) and position2 < len(self._stimuli): self._stimuli[position1], self._stimuli[position2] = \ self._stimuli[position2], self._stimuli[position1] return True else: return False def shuffle_stimuli(self): """Shuffle all stimuli.""" randomize.shuffle_list(self.stimuli) def sort_stimuli(self): """Sort the stimuli according to their IDs from low to high.""" stimuli_new = [] id_list = [x.id for x in self._stimuli] id_list.sort() for _id in id_list: position = [i for i, x in enumerate(self._stimuli) if x.id == _id][0] stimuli_new.append(self._stimuli[position]) self._stimuli = stimuli_new def find_stimulus(self, id): """Find the positions of a stimulus. Parameters ---------- id : int stimulus id to look for Returns ------- pos : int positions as a list or None if not in stimuli list """ positions = [i for i, x in enumerate(self._stimuli) if x.id == id] if positions: return positions def copy(self): """Return a copy of the trial.""" stimlist = [] for stim in self._stimuli: stimlist.append(stim) self._stimuli = None rtn = deepcopy(self) self._stimuli = rtn._stimuli = stimlist return rtn def preload_stimuli(self): """Preload all stimuli in trial. Returns ------- time : int time it took to execute this method in ms """ start = Clock._cpu_time() for stim in self._stimuli: stim.preload() return int((Clock._cpu_time() - start) * 1000) def unload_stimuli(self, keep_surface=False): """Unload all stimuli in trial. Parameters ---------- keep_surface : bool, optional keep the surface after unloading (default = False) Returns ------- time : int time it took to execute this method in ms """ start = Clock._cpu_time() for stim in self._stimuli: stim.unload(keep_surface=keep_surface) return int((Clock._cpu_time() - start) * 1000) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/permute.py0000644000175000017500000000600612314561273024513 0ustar oliveroliver""" The permute module. This module implements permutation of blocks, trials and conditions. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import expyriment.misc as _misc def _empty_square(n): square = [] for x in range(0, n): square.append([]) for _i in range(0, n): square[x].append(None) return square def _square_of_elements(list_, idx_square): """Return a square array of elements. Returns ------- square : list a square array with the elements from the list defined by idx_square e.g.; idx_square[a][b] is the list index of element[a][b]. """ square = _empty_square(len(list_)) for c in range(0, len(list_)): for r in range(0, len(list_)): square[r][c] = list_[idx_square[r][c]] return square def is_permutation_type(type_str): """Return true if the string or value is a know permutation type. Parameters ---------- type_str : string permutation type string """ return (type_str == _misc.constants.P_RANDOM or \ type_str == _misc.constants.P_CYCLED_LATIN_SQUARE or \ type_str == _misc.constants.P_BALANCED_LATIN_SQUARE) def _cycle_list(arr): rtn = arr[1:] rtn.append(arr[0]) return rtn def balanced_latin_square(elements): """A balanced latin square permutation of elements. If elements is an integer the elements=[0,..., elements] is used. Parameters ---------- elements : int or list list of elements or a number """ if type(elements) is list: idx = balanced_latin_square(len(elements)) square = _square_of_elements(elements, idx) else: n = elements # Make n cycled columns [0,1,2,...n-1][1,2,3,...n-1,0][...] columns = cycled_latin_square(n) # Make index list to sort columns [0,1,n-1,3,n-2,4,...] c_idx = [0, 1] take_last = True tmp = range(2, n) for _i in range(2, n): if take_last: c_idx.append(tmp.pop()) else: c_idx.append(tmp.pop(0)) take_last = not take_last # Write sorted colums to square square = _empty_square(n) for c in range(0, n): for r in range(0, n): square[r][c] = columns[c_idx[c]][r] return square def cycled_latin_square(elements): """A cycled latin square permutation of elements. If elements is a integer the elements=[0,..., elements] is used. Parameters ---------- elements : int or list list of elements or a number """ if type(elements) is list: idx = cycled_latin_square(len(elements)) square = _square_of_elements(elements, idx) else: square = [range(0, elements)] for r in range(0, elements - 1): square.append(_cycle_list(square[r])) return square python-expyriment-0.7.0+git34-g55a4e7e/expyriment/design/__init__.py0000644000175000017500000000074712314561273024577 0ustar oliveroliver"""The design package. This package provides several data structures for describing the design of an experiment. See also expyriment.design.extras for more design. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults import permute import randomize from _structure import Experiment, Block, Trial import extras python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/0000775000175000017500000000000012314561273021616 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_screen.py0000644000175000017500000001623712314561273023615 0ustar oliveroliver""" A screen. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame try: import OpenGL.GL as ogl except ImportError: ogl = None import expyriment from _input_output import Output class Screen(Output): """A class implementing a screen output. Each experiment and all stimuli need a screen instance to function. They are expecting this screen instance to be referenced in expyriment._active_exp.screen. Calling expyriment.control.intialize(exp) will automatically create such a screen instance and will additionally reference it in exp.screen for easy access. """ def __init__(self, colour, open_gl, window_mode, window_size): """Create and set up a screen output. Notes ----- CAUTION: We discourage from creating a screen instance manually! Parameters ---------- colour : (int, int, int) colour of the screen open_gl : bool if OpenGL should be used window_mode : bool if screen should be a window window_size : (int, int) size of the window in window_mode, full screen mode if size of window_mode[0]<=0 """ Output.__init__(self) self._colour = colour self._open_gl = open_gl self._fullscreen = not window_mode self._window_size = window_size if ogl is None: warn_message = "PyOpenGL is not installed. \ OpenGL will be deactivated!" print("Warning: " + warn_message) expyriment._active_exp._event_file_warn("Screen,warning," + warn_message) self._open_gl = False if self._open_gl and not self._fullscreen: warn_message = "OpenGL does not support window mode. \ OpenGL will be deactivated!" print("Warning: " + warn_message) expyriment._active_exp._event_file_warn("Screen,warning," + warn_message) self._open_gl = False pygame.display.init() if self._fullscreen: self._window_size = (pygame.display.Info().current_w, pygame.display.Info().current_h) else: self._window_size = window_size if not self._open_gl: if self._fullscreen: self._surface = pygame.display.set_mode(self._window_size, pygame.FULLSCREEN) else: self._surface = pygame.display.set_mode(self._window_size) else: self._surface = pygame.display.set_mode( self._window_size, pygame.DOUBLEBUF | pygame.OPENGL | pygame.FULLSCREEN) ogl_version = ogl.glGetString(ogl.GL_VERSION) if float(ogl_version[0:3]) < 2.0: ogl_extensions = ogl.glGetString(ogl.GL_EXTENSIONS) if "ARB_texture_non_power_of_two" not in ogl_extensions: raise RuntimeError("OpenGL mode is not supported on this \ machine!") pygame.mouse.set_visible(False) pygame.event.set_blocked(pygame.MOUSEMOTION) pygame.event.set_blocked(pygame.MOUSEBUTTONDOWN) pygame.event.set_blocked(pygame.MOUSEBUTTONUP) @property def colour(self): """Getter for colour.""" return self._colour @colour.setter def colour(self, value): """Setter for colour.""" self._colour = value @property def surface(self): """Getter for surface.""" return self._surface @property def open_gl(self): """Getter for open_gl.""" return self._open_gl @property def window_mode(self): """Getter for window_mode.""" return not self._fullscreen @property def window_size(self): """Getter for window_size.""" return self._window_size def update(self): """Update the screen. This will flip the display double buffer. """ pygame.event.pump() pygame.display.flip() if self._open_gl: ogl.glFinish() if self._logging: expyriment._active_exp._event_file_log("Screen,updated", 2) def update_stimuli(self, stimuli): """Update only some stimuli on the screen. Notes ----- This does only work for non OpenGL screens. Parameters ---------- stimuli : list stimulus or a list of stimuli to update """ if type(stimuli) is not list: stimuli = [stimuli] if not self._open_gl: rectangles = [] half_screen_size = (self.size[0] / 2, self.size[1] / 2) for stim in stimuli: pos = stim.absolute_position stim_size = stim.surface_size rect_pos = (pos[0] + half_screen_size[0] - stim_size[0] / 2, - pos[1] + half_screen_size[1] - stim_size[1] / 2) rectangles.append(pygame.Rect(rect_pos, stim_size)) pygame.display.update(rectangles) if self._logging: expyriment._active_exp._event_file_log("Screen,stimuli updated,{0}"\ .format([stim.id for stim in stimuli]), 2) pygame.event.pump() @property def center_x(self): """Getter for X-coordinate of the screen center. Notes ----- Each initialized experiment has its one screen (exp.screen). Please use always the screen of your current experiment. """ return self._window_size[0] / 2 @property def center_y(self): """Getter for the Y-coordinate of the screen center. Notes ----- Each initialized experiment has its one screen (exp.screen). Please use always the screen of your current experiment. """ return self._window_size[1] / 2 @property def size(self): """Getter for the size of the screen. Notes ----- Each initialized experiment has its one screen (exp.screen). Please use always the screen of your current experiment. """ return self._window_size def clear(self): """Clear the screen. This will reset the default experimental background colour. """ if self._open_gl: ogl.glClearColor(float(self._colour[0]) / 255, float(self._colour[1]) / 255, float(self._colour[2]) / 255, 0) ogl.glClear(ogl.GL_COLOR_BUFFER_BIT | ogl.GL_DEPTH_BUFFER_BIT) else: self._surface.fill(self._colour) if self._logging: expyriment._active_exp._event_file_log("Screen,cleared", 2) def save(self, filename): """Save the content of the screen as a picture. Parameters ---------- filename : str name of the file to write (possible extensions are BMP, TGA, PNG, or JPEG, with the default being TGA) """ pygame.image.save(self._surface, filename) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/0000775000175000017500000000000012314561273023124 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/_cedrusresponsedevice_defaults.py0000644000175000017500000000002712314561273031745 0ustar oliveroliver#CedrusDevice defautls python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/_midiin.py0000644000175000017500000001151012314561273025102 0ustar oliveroliver"""MIDI input. This module contains a class implementing a MIDI input device. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import _midiin_defaults as defaults import expyriment from expyriment.misc._timer import get_time from expyriment.io._keyboard import Keyboard from expyriment.io._input_output import Input import time try: from pygame import midi as _midi _midi.init() except: _midi = None class MidiIn(Input): """A class implementing a MIDI input. **EXPERIMENTAL!** Due to a bug in Pygame's midi module, closing a MidiIn (or the programme) will cause an error message. Until this is fixed in Pygame, MidiIn will stay in extras. """ @staticmethod def get_devices(): """Get a list of all MIDI input devices connected to the system.""" if _midi is None: return indevices = [] all_ids = _midi.get_count() for device_id in all_ids: info = _midi.get_device_info(device_id) if info[2] == 1: indevices.add([device_id, info[1]]) return indevices def __init__(self, device, buffer_size=None): """Create a MIDI input. Parameters ---------- device : int or str id or name of the MIDI device buffer_size : int, optional number of events to be buffered """ import types if type(_midi) is not types.ModuleType: raise ImportError("""Sorry, MIDI input is not supported on this computer.""") if not expyriment._active_exp.is_initialized: raise RuntimeError( "Cannot create MidiIn before expyriment.initialize()!") _midi.init() Input.__init__(self) self._id = device if buffer_size is None: buffer_size = defaults.midiin_buffer_size self._buffer_size = buffer_size self.input = _midi.Input(device, buffer_size) @property def id(self): """Getter for id.""" return self._id @property def buffer_size(self): """Getter for buffer_size.""" return self._buffer_size def read(self, num_events=1): """Read MIDI events from device. Parameters ---------- num_events : int, optional number of events to read (default=1) Returns ------- out : timestpamed A timestpamed midi event will look like this: [status, data1, data2, data3], timestamp] """ if self.input.poll(): if self._logging: expyriment._active_exp._event_file_log( "MIDI In ({0}),received".format(self.id), 2) return self.input.read(num_events) def clear(self): """Clear the input buffer. This can take more than 1 ms! """ for _i in range(self._buffer_size): self.input.read(1) if self._logging: expyriment._active_exp._event_file_log( "MIDI In ({0}),cleared".format(self.id), 2) def wait(self, events, duration=None): """Wait for (a) certain event(s). Events to wait for are in the form of a list with 4 elements and do not include a timestamp: [status, data1, data2, data3] Parameters ---------- events : int or list event(s) to wait for duration : int, optional maximal time to wait in ms Returns ------- evt : int found event rt : int reaction timein ms """ start = get_time() rt = None _event = None self.clear() if type(events) is list and \ len(events) == 4 and \ type(events[0]) is int and \ type(events[1]) is int and \ type(events[2]) is int and \ type(events[3]) is int: events = [events] done = False while not done: expyriment._active_exp._execute_wait_callback() event = self.read(1) if event is not None and event[0][0] in events: rt = int((get_time() - start) * 1000) _event = event[0][0] done = True break if Keyboard.process_control_keys(): done = True break if duration: if int((get_time() - start) * 1000) >= duration: done = True break time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log( "MIDI In ({0}),received,{1},wait".format(self.id, _event), 2) return _event, rt python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/_cedrusresponsedevice.py0000644000175000017500000002070412314561273030062 0ustar oliveroliver"""Cedrus XID response device. This module contains a class implementing a Cedrus XID response device. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import types try: import pyxid as _pyxid except: _pyxid = None import expyriment from expyriment.io._input_output import Input class CedrusResponseDevice(Input): """A class implementing a Cedrus XID response device. Notes ----- The CedrusResponseDevice class requires a free Python package for Cedrus devices called "pyxid". For installation instructions see Expyriment online documentation: http://docs.expyriment.org/Hardware.html The class does not use the hardware timer, due to the known bug in the Cedrus hardware. Events will be time stamped by Expyriment. Thus, ensure constant polling / checking when not using the wait function. To install Cedrus resonse device under Linux, you have to set the USB product ID. To do so, edit the file /etc/modules and add the following line:: ftdi_sio vendor=0403 product=f228 """ def __init__(self, device_ID=0, error_screen=True): """Create a Cedrus Device Input. Notes ----- If no Cedrus device is connected, an error text screen will be presented informing that the device could not be found and suggesting to check the connection and to switch on the device. After keypress the class tries to reconnect with the device. Use to quit this procedure. Parameters ---------- device_id : int, optional device ID (default=0). Only required if more than one Cedrus Devices are connected. error_screen : bool, optional set False to switch off the 'device not found' error screen. An exception will be raise instead (default=True) """ Input.__init__(self) if type(_pyxid) is not types.ModuleType: message = """CedrusDevice can not be initialized, because the Python package 'pyxid' is not installed. See Expyriment online documentation.""" raise ImportError(message) while True: devices = _pyxid.get_xid_devices() if len(devices) < 1: message = "Could not find a Cedrus Device. Please check the connection and \n"\ + " ensure that the device is switch on." else: if not devices[device_ID].is_response_device(): message = "Cedrus Device #{0} is not a response device.".format( device_ID) else: self._xid = devices[device_ID] break if error_screen and expyriment._active_exp.is_initialized: expyriment.stimuli.TextScreen("Error", message + " Press a key to reconnect to the device.").present() expyriment._active_exp.keyboard.wait() expyriment.stimuli.BlankScreen().present() expyriment._active_exp.clock.wait(300) else: raise IOError(message) self._xid.reset_base_timer() self._xid.reset_rt_timer() self._device_ID = device_ID self._buffer = expyriment.misc.Buffer(name="Cedrus Device {0}".format( device_ID)) def __str__(self): return self._xid.__str__() @property def id(self): """Getter for XID device id.""" return self._device_ID @property def buffer(self): """Getter for buffer.""" return self._buffer @property def xid_interface(self): """Getter for xid device interface.""" return self._xid def poll_responses(self): """Poll the Cedrus response device and copies the available response in the buffer. Notes ----- Release key events get a code larger 999 (1000 + key code). Returns ------- out : bool True is a new response was available. """ self._xid.poll_for_response() new_event = False # copy xid cue to _buffer and poll while self._xid.response_queue_size() > 0: new_event = True response = self._xid.get_next_response() if response['pressed'] == False: response['key'] = 1000 + response['key'] self._buffer.add_event(response['key']) if self._logging: expyriment._active_exp._event_file_log( "CedrusResponseDevice {0},received,{1},poll".format( self._device_ID, response['key']), 2) self._xid.poll_for_response() return new_event def clear(self): """Clear device and all events in the response cues.""" self._xid.poll_for_response() while self._xid.response_queue_size() > 0: self._xid.clear_response_queue() self._xid.poll_for_response() self._buffer.clear() if self._logging: expyriment._active_exp._event_file_log( "CedrusResponseDevice,cleared", 2) def check(self, codes=None): """Check for a specific response code occurred since last clear(). The function polls the device and returns the first event found in the buffer (i.e., since last clear) or None. Parameters ---------- codes : int or list, optional key codes to check for Returns ------- key : int code of pressed key rt : int reaction time in ms """ self.poll_responses() if self._buffer.get_size() > 0: if codes is None: return self._buffer.memory[0] else: if type(codes) is not types.ListType: codes = [codes] for elem in self._buffer.memory: if elem[0] in codes: return elem return None return None def wait(self, codes=None, duration=None, no_clear_buffer=False, check_for_control_keys=True): """Wait for responses defined as codes. The functions returns the found key code and the reaction time, that is, the time relative to the called of wait. By default the buffer will be cleared() before waiting. Notes ----- The function checks for control keys (quit and pause) by default. Thus, keyboard events will be cleared from the cue and cannot be received by a Keyboard().check() anymore! Parameters ---------- codes : int or list, optional codes to wait for duration : int, optional maximal time to wait in ms no_clear_buffer : bool, optional do not clear the buffer. In this case RT could be negative, if the event is already in the buffer (default = False) check_for_control_keys : bool, optional checks if control key has been pressed (default = True) Returns ------- key : int code of pressed key rt : int reaction time in ms """ start = self._buffer.clock.time if not no_clear_buffer: self.clear() while True: expyriment._active_exp._execute_wait_callback() if duration is not None: if int(self._buffer.clock.time - start) > duration: return (None, None) found = self.check(codes) if found is not None: found = (found[0], found[1] - start) break if check_for_control_keys: if expyriment._active_exp.keyboard.process_control_keys(): break expyriment._active_exp._event_file_log( "CedrusResponseDevice,received,{0},wait".format( found)) return found if type(_pyxid) is types.ModuleType: @staticmethod def _self_test(experiment): result = {} result['CedrusResponseDevice'] = "" # TODO: Implement test! return result python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/_midiout_defaults.py0000644000175000017500000000010212314561273027165 0ustar oliveroliver# MidiOut defaults midiout_buffer_size = 1024 midiout_latency = 0 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/_midiin_defaults.py0000644000175000017500000000005412314561273026772 0ustar oliveroliver# MidiIn defaults midiin_buffer_size = 1024 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/_midiout.py0000644000175000017500000001213012314561273025302 0ustar oliveroliver"""MIDI output. This module contains a class implementing a MIDI output device. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import _midiout_defaults as defaults import expyriment from expyriment.io._input_output import Output try: from pygame import midi as _midi _midi.init() except: _midi = None class MidiOut(Output): """A class implementing a MIDI output. **EXPERIMENTAL!** Due to a bug in Pygame's midi module, closing a MidiOut (or the programme) will cause an error message. Until this is fixed in Pygame, MidiOut will stay in extras. """ @staticmethod def get_devices(): """Get a list of all MIDI output devices connected to the system.""" if _midi is None: return outdevices = [] all_ids = _midi.get_count() for device_id in all_ids: info = _midi.get_device_info(device_id) if info[3] == 1: outdevices.add([device_id, info[1]]) return outdevices def __init__(self, device, latency=None, buffer_size=None): """Create a MIDI output. Parameters ---------- device : int or str id or name of the MIDI device latency : int, optional delay in ms applied to timestamp buffer_size : int, optional number of events to be buffered """ import types if type(_midi) is not types.ModuleType: raise ImportError("""Sorry, MIDI output is not supported on this computer.""") if not expyriment._active_exp.is_initialized: raise RuntimeError( "Cannot create MidiOut before expyriment.initialize()!") _midi.init() Output.__init__(self) self._id = device if buffer_size is None: buffer_size = defaults.midiout_buffer_size self._buffer_size = buffer_size if latency is None: latency = defaults.midiout_latency self._latency = latency self.output = _midi.Output(device, latency, buffer_size) @property def id(self): """Getter for id.""" return self._id @property def buffer_size(self): """Getter for buffer_size.""" return self._buffer_size @property def latency(self): """Getter for latency.""" return self._latency def close(self, abort=False): """Close the MIDI interface. Parameters ---------- abort : bool, optional abort messages in the buffer (default=True) """ if abort: self.output.abort() self.output.close() def abort(self): """Terminates outgoing messages immediately.""" self.output.abort() def send(self, event_list): """Send a list of MIDI events. Each event should have the following format: [status, data1, data2, data3], timestamp] Notes ----- The data fields are optional. Parameters ---------- event_list : list list of events to send """ self.output.write(event_list) def send_short(self, status, data1=0, data2=0): """Send MIDI events of 3 bytes or less. Parameters ---------- status : int status of the event to send data1 : int, optional data1 of the event to send data2 : int, optional data2 of the event to send """ self.output.write_short(status, data1, data2) def send_sysex(self, timestamp, message): """Send a System Exlusive message. Parameters ---------- timestamp : int when (in ms) to send the message message : sit or list message to send """ self.output.wirte_sys_ex(timestamp, message) def select_instrument(self, instrument_id, channel=0): """Select an instrument. Parameters ---------- instrument_id : int id (0-127) of the instrument channel : int, optional MIDI channel for the instrument (default=0) """ self.output.set_instrument(instrument_id, channel) def send_note_on(self, note, velocity=None, channel=0): """Send a note-on event. Parameters ---------- note : int note value velocity : int, optional velocity of the note channel : int, optional MIDI channel of the note (default=0) """ self.output.note_on(note, velocity, channel) def send_note_off(self, note, velocity=None, channel=0): """Send a note-off event. Parameters ---------- note : int note value velocity : int, optional velocity of the note channel : int, optional MIDI channel of the note (default=0) """ self.output.note_off(note, velocity, channel) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/defaults.py0000644000175000017500000000113212314561273025300 0ustar oliveroliver""" Default settings for io.extras. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugin_defaults(__file__), _importer_functions.import_plugin_defaults_from_home(__file__)]: for _defaults in _plugins: exec(_defaults) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/extras/__init__.py0000644000175000017500000000123212314561273025231 0ustar oliveroliver""" The io extra package. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os import defaults from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugins(__file__), _importer_functions.import_plugins_from_settings_folder(__file__)]: for _plugin in _plugins: try: exec(_plugins[_plugin]) except: print("Warning: Could not import {0}".format( _os.path.dirname(__file__) + _os.sep + _plugin)) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_textinput.py0000644000175000017500000004033212314561273024373 0ustar oliveroliver""" A text input box. This module contains a class implementing a text input box for user input. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame try: import android except ImportError: android = None try: import android.show_keyboard as android_show_keyboard import android.hide_keyboard as android_hide_keyboard except ImportError: android_show_keyboard = android_hide_keyboard = None import defaults from expyriment.misc import find_font, unicode2str import expyriment from _input_output import Input class TextInput(Input): """A class implementing a text input box.""" def __init__(self, message="", position=None, ascii_filter=None, length=None, message_text_size=None, message_colour=None, message_font=None, message_bold=None, message_italic=None, user_text_size=None, user_text_bold=None, user_text_font=None, user_text_colour=None, background_colour=None, frame_colour=None, gap=None, screen=None, background_stimulus=None): """Create a text input box. Notes ----- This stimulus is not optimized for timing accurate presentation! Parameters ---------- message : str, optional message to show position : (int, int), optional position of the TextInput canvas length : int, optional the length of the text input frame in number of charaters ascii_filter : list, optional list of ASCII codes to filter for message_text_size : int, optional text size of the message message_colour : (int, int, int), optional text colour of the message message_font : str, optional text font of the message message_bold : bool, optional True if message text should be bold message_italic : bool, optional True if message text should be italic user_text_size : int, optional text size of the user input user_text_font : str, optional text font of the user input user_text_colour : (int, int ,int), optional text colour of the user input user_text_bold : bool, optional True if user text should be bold background_colour : (int, int, int), optional frame_colour : (int, int, int) colour of the frame gap : int, optional gap between message and user input screen : io.Screen, optional screen to present on background_stimulus : visual Expyriment stimulus, optional The background stimulus is a second stimulus that will be presented together with the TextInput. For both stimuli overlap TextInput will appear on top of the background_stimulus """ if not expyriment._active_exp.is_initialized: raise RuntimeError( "Cannot create TextInput before expyriment.initialize()!") Input.__init__(self) self._message = message if position is not None: self._position = position else: self._position = defaults.textinput_position if ascii_filter is not None: self._ascii_filter = ascii_filter else: self._ascii_filter = defaults.textinput_ascii_filter if length is not None: self._length = length else: self._length = defaults.textinput_length if message_text_size is None: message_text_size = defaults.textinput_message_text_size if message_text_size is not None: self._message_text_size = message_text_size else: self._message_text_size = expyriment._active_exp.text_size if message_colour is None: message_colour = defaults.textinput_message_colour if message_colour is not None: self._message_colour = message_colour else: self._message_colour = expyriment._active_exp.foreground_colour if message_font is None: message_font = defaults.textinput_message_font if message_font is not None: self._message_font = find_font(message_font) else: self._message_font = find_font(expyriment._active_exp.text_font) try: _font = pygame.font.Font( unicode2str(self._message_font, fse=True), 10) except: raise IOError("Font '{0}' not found!".format(message_font)) if message_bold is not None: self._message_bold = message_bold else: self._message_bold = defaults.textinput_message_bold if message_italic is not None: self._message_italic = message_italic else: self._message_italic = defaults.textinput_message_italic if user_text_size is None: user_text_size = defaults.textinput_user_text_size if user_text_size is not None: self._user_text_size = user_text_size else: self._user_text_size = expyriment._active_exp.text_size if user_text_bold is not None: self._user_text_bold = user_text_bold else: self._user_text_bold = defaults.textinput_user_text_bold if user_text_font is None: user_text_font = defaults.textinput_user_text_font if user_text_font is not None: self._user_text_font = find_font(user_text_font) else: self._user_text_font = find_font(expyriment._active_exp.text_font) try: _font = pygame.font.Font( unicode2str(self._user_text_font, fse=True), 10) except: raise IOError("Font '{0}' not found!".format(user_text_font)) if user_text_colour is None: user_text_colour = defaults.textinput_user_text_colour if user_text_colour is not None: self._user_text_colour = user_text_colour else: self._user_text_colour = expyriment._active_exp.foreground_colour if background_colour is None: background_colour = \ defaults.textinput_background_colour if background_colour is not None: self._background_colour = background_colour else: self._background_colour = \ expyriment._active_exp.background_colour if frame_colour is None: frame_colour = defaults.textinput_frame_colour if frame_colour is not None: self._frame_colour = frame_colour else: self._frame_colour = expyriment._active_exp.foreground_colour if gap is not None: self._gap = gap else: self._gap = defaults.textinput_gap if screen is not None: self._screen = screen else: self._screen = expyriment._active_exp.screen if background_stimulus is not None: if background_stimulus.__class__.__base__ in \ [expyriment.stimuli._visual.Visual, expyriment.stimuli.Shape]: self._background_stimulus = background_stimulus else: raise TypeError("{0} ".format(type(background_stimulus)) + "is not a valid background stimulus. " + "Use an expyriment visual stimulus.") else: self._background_stimulus = None self._user = [] self._user_text_surface_size = None self._max_size = None self._message_surface_size = None self._canvas = None self._canvas_size = None @property def message(self): """Getter for message""" return self._message @property def position(self): """Getter for position""" return self._position @property def ascii_code_filter(self): """Getter for filter""" return self._ascii_filter @ascii_code_filter.setter def ascii_code_filter(self, value): """Getter for filter""" self._ascii_filter = value @property def message_text_size(self): """Getter for message_text_size""" return self._message_text_size @property def length(self): """Getter for length""" return self._length @property def message_colour(self): """Getter for message_colour""" return self._message_colour @property def message_font(self): """Getter for message_font""" return self._message_font @property def message_bold(self): """Getter for message_bold""" return self._message_bold @property def message_italic(self): """Getter for message_italic""" return self._message_italic @property def user_text_size(self): """Getter for user_text_size""" return self._user_text_size @property def user_text_bold(self): """Getter for user_text_bold""" return self._user_text_bold @property def user_text_font(self): """Getter for user_text_font""" return self._user_text_font @property def user_text_colour(self): """Getter for user_text_colour""" return self._user_text_colour @property def background_colour(self): """Getter for background_colour""" return self._background_colour @property def frame_colour(self): """Getter for frame_colour""" return self._frame_colour @property def gap(self): """Getter for gap""" return self._gap @property def screen(self): """Getter for screen""" return self._screen @property def background_stimulus(self): """Getter for background_stimulus""" return self._background_stimulus def _get_key(self): """Get a key press.""" while True: expyriment._active_exp._execute_wait_callback() event = pygame.event.poll() if event.type == pygame.KEYDOWN: return event.key, event.unicode def _create(self): """Create the input box.""" tmp = expyriment.stimuli.TextLine(text=self._length * "X", text_font=self.user_text_font, text_size=self.user_text_size, text_bold=self.user_text_bold) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._max_size = tmp.surface_size message_text = expyriment.stimuli.TextLine( text=self._message, text_font=self.message_font, text_size=self.message_text_size, text_bold=self.message_bold, text_italic=self.message_italic, text_colour=self.message_colour, background_colour=self._background_colour) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._message_surface_size = message_text.surface_size self._canvas = expyriment.stimuli.Canvas(size=( max(self._max_size[0] + 12, self._message_surface_size[0]), self._message_surface_size[1] + self._max_size[1] + self._gap + 5), colour=self._background_colour, position=self._position) #self._canvas = expyriment.stimuli.BlankScreen() expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._canvas._set_surface(self._canvas._get_surface()) self._canvas_size = self._canvas.surface_size pygame.draw.rect(self._canvas._get_surface(), self._background_colour, (self._canvas_size[0] / 2 - self._max_size[0] / 2 - 6, self._message_surface_size[1] + self._gap, self._max_size[0] + 12, self._max_size[1] + 5), 0) pygame.draw.rect(self._canvas._get_surface(), self._frame_colour, (self._canvas_size[0] / 2 - self._max_size[0] / 2 - 6, self._message_surface_size[1] + self._gap, self._max_size[0] + 12, self._max_size[1] + 5), 1) if len(self._message) != 0: self._canvas._get_surface().blit( message_text._get_surface(), (self._canvas.surface_size[0] / 2 - self._message_surface_size[0] / 2, 0)) background = expyriment.stimuli.BlankScreen( colour=self._background_colour) if self._background_stimulus is not None: self._background_stimulus.plot(background) self._canvas.plot(background) background.present() background.present() # for flipping with double buffer background.present() # for flipping with tripple buffer def _update(self): """Update the input box.""" user_canvas = expyriment.stimuli.Canvas( size=self._max_size, colour=self._background_colour) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 user_canvas._set_surface(user_canvas._get_surface()) user_canvas_size = user_canvas.surface_size offset = 2 + user_canvas_size[1] % 2 user_canvas.position = (self._canvas.absolute_position[0], self._canvas.absolute_position[1] + self._canvas_size[1] / 2 - user_canvas_size[1] / 2 - self._message_surface_size[1] - self._gap - offset) user_text = expyriment.stimuli.TextLine( text="".join(self._user), text_font=self.user_text_font, text_size=self.user_text_size, text_bold=self.user_text_bold, text_colour=self.user_text_colour, background_colour=self.background_colour) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._user_text_surface_size = user_text.surface_size user_canvas._get_surface().blit(user_text._get_surface(), (0, 2)) user_canvas.present(clear=False) def get(self, default_input=""): """Get input from user. Notes ----- This displays and updates the input box automatically. Pressing ENTER returns the user input. Pressing ESC quits, returning an empty string. Parameters ---------- default_input : str, optional default input in the textbox """ if android_show_keyboard is not None: android_show_keyboard() self._user = [] for char in default_input: self._user.append(char) self._create() self._update() if self._ascii_filter is None: ascii_filter = range(0, 256) else: ascii_filter = self._ascii_filter while True: inkey, string = self._get_key() if inkey == pygame.K_BACKSPACE: self._user = self._user[0:-1] elif inkey == pygame.K_RETURN: break elif inkey != pygame.K_LCTRL or pygame.K_RCTRL: if not self._user_text_surface_size[0] >= self._max_size[0]: if android is not None: if inkey in ascii_filter: self._user.append(chr(inkey)) else: if string and ord(string) in ascii_filter: self._user.append(string) self._update() got = "".join(self._user) if self._logging: expyriment._active_exp._event_file_log("TextInput,entered,{0}" .format(unicode2str(got))) if android_hide_keyboard is not None: android_hide_keyboard() return got if __name__ == '__main__': from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() textinput = TextInput(message="Subject Number:", message_colour=(160, 70, 250), user_text_size=30, user_text_colour=(255, 150, 50), frame_colour=(70, 70, 70)) print textinput.get() python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_eventbuttonbox.py0000644000175000017500000000230312314561273025411 0ustar oliveroliver""" An event button box. This module contains a class implementing an event button box. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from _streamingbuttonbox import StreamingButtonBox class EventButtonBox(StreamingButtonBox): """A class implementing an event button box input.""" def __init__(self, interface): """Create an event button box input. Compared to a StreamingButtonBox, an EventButtonBox has no baseline (baseline=None). The methods wait() and check() are therefore responsive to every incomming interface event. Parameters ---------- interface : io.SerialPort or io.ParallelPort an interface object """ StreamingButtonBox.__init__(self, interface, None) @property def baseline(self): """Getter for baseline""" return self._baseline @baseline.setter def baseline(self, value): """Setter for baseline.""" print("Warning: A baseline cannot be defined for an EventButtonBox!") self._baseline = None python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_gamepad.py0000644000175000017500000001517112314561273023730 0ustar oliveroliver"""A gamepad. This module contains a class implementing a pygame gamepad. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame import time import expyriment from expyriment.misc._timer import get_time from _keyboard import Keyboard from _input_output import Input, Output pygame.joystick.init() class GamePad(Input, Output): """A class for creating gamepad/joystick input.""" @staticmethod def get_gampad_count(): """Get the number of gamepads/joysticks connected to the system.""" return pygame.joystick.get_count() def __init__(self, gamepad_id, track_button_events=True, track_motion_events=False): """Create a gamepad/joystick input. Parameters ---------- gamepad_id : int id of the gamepad track_button_events : bool, optional Track button events (default=True) track_motion_events : bool, optional Track motion events (default=False) """ if not expyriment._active_exp.is_initialized: raise RuntimeError( "Cannot create GamePad before expyriment.initialize()!") Input.__init__(self) Output.__init__(self) self.track_button_events = track_button_events self.track_motion_events = track_motion_events self._joystick = pygame.joystick.Joystick(gamepad_id) self._joystick.init() @property def track_button_events(self): """Getter for track_button_events.""" return self._track_button_events @track_button_events.setter def track_button_events(self, value): """Setter for track_button_events. Switch on/off the processing of button events. """ self._track_button_events = value if value: pygame.event.set_allowed(pygame.JOYBUTTONDOWN) pygame.event.set_allowed(pygame.JOYBUTTONUP) else: pygame.event.set_blocked(pygame.JOYBUTTONDOWN) pygame.event.set_blocked(pygame.JOYBUTTONUP) @property def track_motion_events(self): """Getter for track_motion_events.""" return self._track_motion_events @track_motion_events.setter def track_motion_events(self, value): """Setter for track_motion_events. Switch on/off the processing of motion events. """ self._track_motion_events = value if value: pygame.event.set_allowed(pygame.JOYAXISMOTION) pygame.event.set_allowed(pygame.JOYBALLMOTION) pygame.event.set_allowed(pygame.JOYHATMOTION) else: pygame.event.set_blocked(pygame.JOYAXISMOTION) pygame.event.set_blocked(pygame.JOYBALLMOTION) pygame.event.set_blocked(pygame.JOYHATMOTION) @property def id(self): """Getter for id.""" return self._joystick.get_id() @property def name(self): """Getter for name.""" return self._joystick.get_name() @property def joystick(self): """Getter for joystick.""" return self._joystick def get_numaxes(self): """Get the number of axes.""" return self._joystick.get_numaxes() def get_axis(self, axis): """Get current axis state. Parameters ---------- axis : int axis to get the current state from """ pygame.event.pump() return self._joystick.get_axis(axis) def get_numballs(self): """Get the number of balls.""" return self._joystick.get_numballs() def get_ball(self, ball): """Get current ball state. Parameters ---------- ball : int ball to get the current state from """ pygame.event.pump() return self._joystick.get_ball(ball) def get_numbuttons(self): """Get the number of buttons.""" return self._joystick.get_numbuttons() def get_button(self, button): """Get current button state. Parameters ---------- button : int button to get the current state from """ pygame.event.pump() return self._joystick.get_button(button) def get_numhats(self): """Get the number of hats.""" return self._joystick.get_numhats() def get_hat(self, hat): """Get current hat state. Parameters ---------- hat : int hat to get the current state from """ pygame.event.pump() return self._joystick.get_hat(hat) def clear(self): """Clear gamepad events from cue.""" pygame.event.clear(pygame.JOYBUTTONDOWN) pygame.event.clear(pygame.JOYBUTTONUP) pygame.event.clear(pygame.JOYAXISMOTION) pygame.event.clear(pygame.JOYBALLMOTION) pygame.event.clear(pygame.JOYHATMOTION) if self._logging: expyriment._active_exp._event_file_log("GamePad,cleared", 2) def wait_press(self, buttons=None, duration=None): """Wait for gamepad button press. Returns the found button and the reaction time. Parameters ---------- buttons : int or list, optional specific buttons to wait for duration : int, optional maximal time to wait in ms Returns ------- button : int button _id of the pressed button rt : int reaction time in ms """ start = get_time() rt = None _button = None self.clear() if buttons is None: buttons = range(self.get_numbuttons()) if type(buttons) is not list: buttons = [buttons] done = False while not done: expyriment._active_exp._execute_wait_callback() for button in buttons: if self.get_button(button): _button = button rt = int((get_time() - start) * 1000) done = True break if _button is not None or Keyboard.process_control_keys(): done = True break if duration: if int((get_time() - start) * 1000) >= duration: done = True break time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log( "Gamepad,received,{0},wait_press".format(_button)) return _button, rt python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_touchscreenbuttonbox.py0000644000175000017500000002050712314561273026620 0ustar oliveroliver""" A touchscreen button box. This module contains a class implementing a touchscreen button box. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import expyriment from expyriment.misc._timer import get_time from _input_output import Input class TouchScreenButtonBox(Input): """A class implementing a TouchScreenButtonBox.""" def __init__(self, button_fields, stimuli=[], background_stimulus=None): """Initialize a touchscreen button box. Parameters ---------- button_fields : visual Expyriment stimulus or list of stimuli The button fields defines the area on which a click action will be registered. stimuli : visual Expyriment stimulus or list of stimuli, optional Additional visual stimuli that will be presented together with the button fields. Stimuli are plotted on top of the button_fields. background_stimulus : visual Expyriment stimulus, optional The background stimulus on which the touschscreen button fields are presented. Importantly, background_stimulus has to have size of the screen. Notes ----- Every visual Expyriment stimulus can serve as a touchscreen button field. If the TouchScreenButtonBox is presented, it can be checked for events using the methods 'check' and 'wait'. """ Input.__init__(self) if type(button_fields) is not list: button_fields = [button_fields] if type(stimuli) is not list: stimuli = [stimuli] self._mouse = expyriment._active_exp.mouse self._last_touch_position = None self._canvas = None self._button_fields = [] self._stimuli = [] self.background_stimulus = background_stimulus map(self.add_button_field, button_fields) map(self.add_stimulus, stimuli) def add_button_field(self, button_field): """Add a touchscreen button fields. Parameters ---------- button_field : visual Expyriment stimulus """ if not isinstance(button_field, expyriment.stimuli._visual.Visual): raise TypeError("Button field has to a visual Expyriment stimulus") self._button_fields.append(button_field) self._canvas = None def add_stimulus(self, stimulus): """Add additional stimulus The added stimulus will be presented together with the button fields. Parameters ---------- stimulus : visual Expyriment stimulus """ if not isinstance(stimulus, expyriment.stimuli._visual.Visual): raise TypeError("Additional stimuli has to a visual Expyriment stimulus") self._stimuli.append(stimulus) self._canvas = None @property def last_touch_position(self): """getter for the last touch position (x,y)""" return self._last_touch_position @property def button_field(self): """getter of button fields (list of visual Expyriment stimuli)""" return self._button_fields @property def stimuli(self): """getter of additional stimuli (list of visual Expyriment stimuli)""" return self._stimuli @property def background_stimulus(self): """Getter of background stimulus. Background stimulus, on which the button fields and the additional stimuli will be presented. (visual Expyriment stimuli) """ return self._background_stimulus @background_stimulus.setter def background_stimulus(self, stimulus): """Setter background stimulus""" if stimulus is None: self._background_stimulus = expyriment.stimuli.BlankScreen() elif not isinstance(stimulus, expyriment.stimuli._visual.Visual): raise TypeError("Background stimulus has to be be a " + "visual Expyriment stimulus") else: self._background_stimulus = stimulus self._canvas = None def clear_event_buffer(self): """Clear the event buffer of the touchscreen/mouse input device.""" if self._mouse is not None: self._mouse.clear() def create(self): """Create the touchscreen buttonbox. Prepares the button fields and additional stimuli for fast presentation. """ self._canvas = self._background_stimulus.copy() if len(self._button_fields) < 1: raise RuntimeError("No button field defined!") map(lambda x:x.plot(self._canvas), self._button_fields) map(lambda x:x.plot(self._canvas), self._stimuli) self._canvas.preload() def destroy(self): if self._canvas is not None: self._canvas.unload() self._canvas = None def show(self): """Present touchscreen buttons.""" if self._canvas is None: self.create() self._canvas.present() def check(self, button_fields=None, check_for_control_keys=True): """Check if a button field is clicked. Parameters ---------- button_fields : Expyriment stimulus or list of stimuli, optional The button fields that will be checked for. check_for_control_keys : bool, optional checks if control key has been pressed (default=True) Returns ------- pressed_button_field : Expyriment stimulus or list of stimuli, optional The button fields that will be checked for. touch_time : integer The time when the button was touched. Function might return delayed, because button field comparison (after touch) takes time. The return time is most accurate. Notes ----- Don't forget to show the TouchScreenButtonBox. """ if button_fields is not None and type(button_fields) is not list: button_fields = [button_fields] if check_for_control_keys: expyriment.io.Keyboard.process_control_keys() pressed_button_field = None touch_time = None if self._mouse.get_last_button_down_event() is not None: touch_time = get_time() self._last_touch_position = self._mouse.position pressed_button_field = self._get_button_field(self._last_touch_position, button_fields) if self._logging and pressed_button_field is not None: expyriment._active_exp._event_file_log( "{0},received, button press,check".format( self.__class__.__name__)) return pressed_button_field, touch_time def _get_button_field(self, position, button_fields): """ helper function return the button field of the position""" if button_fields is None: button_fields = self._button_fields for bf in button_fields: if bf.overlapping_with_position(position): return bf return None def wait(self, duration=None, button_fields=None, check_for_control_keys=True): """Wait for a touchscreen button box click. Parameters ---------- button_fields : Expyriment stimulus or list of stimuli, optional The button fields that will be checked for. duration : int, optional maximal time to wait in ms Returns ------- pressed_button_field : Expyriment stimulus or None the button field defined by a stimulus that has been pressed rt : int reaction time Notes ----- Don't forget to show the TouchScreenButtonBox. """ start = get_time() self.clear_event_buffer() while True: expyriment._active_exp._execute_wait_callback() pressed_button_field, touch_time = self.check(button_fields, check_for_control_keys) if pressed_button_field is not None: rt = int((touch_time - start) * 1000) break elif (duration is not None and rt>= duration): pressed_button_field, rt = None, None break return pressed_button_field, rt python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_input_output.py0000644000175000017500000000142212314561273025103 0ustar oliveroliver""" The base module of io. This module contains the base classes for input and output. All classes in this module should be called directly via expyriment.io.*. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import expyriment class Input(expyriment._Expyriment_object): """A class implementing a general input.""" def __init__(self): """Create an input.""" expyriment._Expyriment_object.__init__(self) class Output(expyriment._Expyriment_object): """A class implementing a general output.""" def __init__(self): """Create an output.""" expyriment._Expyriment_object.__init__(self) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_serialport.py0000644000175000017500000004570212314561273024521 0ustar oliveroliver""" Input and output serial port. This module contains a class implementing serial port input/output. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import atexit from sys import platform from os import listdir try: import serial except: serial = None import defaults import expyriment from _input_output import Input, Output from expyriment.misc import ByteBuffer, Clock class SerialPort(Input, Output): """A class implementing a serial port input and output.""" def __init__(self, port, baudrate=None, bytesize=None, parity=None, stopbits=None, timeout=None, xonxoff=None, rtscts=None, dsrdtr=None, input_history=None, os_buffer_size=None, clock=None): """Create a serial port input and output. The port argument will accept the number of the port (e.g. 0 for COM1) as well as a string describing the full port location ("COM1" or "/dev/ttyS0"). Notes ----- An input_history can be used to overcome the size limitation of the receive buffer of the operating system. An input_history consists of a misc.ByteBuffer instance. In order to not miss any input, the serial port has to be updated regularly (i.e. calling read_input() or clear() before the receive buffer will be full). If the receive buffer size is set correctly, a warning will be given, when the input_history was not updated fast enough. Importantly, the fuller the receive buffer is, the longer clearing and polling will take (this can be more than 1 ms!), since all the bytes have to be transfered to the input_history. Parameters ---------- port : int or str port to use baudrate : int, optional bytesize : int, optional parity : str, optional parity:'E'=even, 'O'=odd, 'N'=none stopbits : int, optional timeout : int, optional the timeout for read(): -1=block xonxoff : int, optional rtscts : int, optional dsrdtr : int, optional input_history : bool, optional True if an input_history should be used os_buffer_size : int, optional the size of the receive input_history provided by the operating system in bytes clock : misc.Clock, optional an experimental clock (optional) """ import types if type(serial) is not types.ModuleType: message = """SerialPort can not be initialized. The Python package 'pySerial' is not installed.""" raise ImportError(message) if float(serial.VERSION) < 2.5: raise ImportError("Expyriment {0} ".format(__version__) + "is not compatible with PySerial {0}.".format( serial.VERSION) + "\nPlease install PySerial 2.5 or higher.") Input.__init__(self) Output.__init__(self) if baudrate is None: baudrate = defaults.serialport_baudrate if bytesize is None: bytesize = defaults.serialport_bytesize if parity is None: parity = defaults.serialport_parity if stopbits is None: stopbits = defaults.serialport_stopbits if timeout is None: timeout = defaults.serialport_timeout if timeout == -1: timeout = None if xonxoff is None: xonxoff = defaults.serialport_xonxoff if rtscts is None: rtscts = defaults.serialport_rtscts if dsrdtr is None: dsrdtr = defaults.serialport_dsrdtr if clock is not None: self._clock = clock else: if expyriment._active_exp.is_initialized: self._clock = expyriment._active_exp.clock else: self._clock = Clock() if input_history is None: input_history = defaults.serialport_input_history if input_history is True: self._input_history = ByteBuffer( name="SerialPortBuffer (Port {0})".format(repr(port)), clock=self._clock) else: self._input_history = False if os_buffer_size is None: os_buffer_size = defaults.serialport_os_buffer_size self._os_buffer_size = os_buffer_size self._serial = serial.Serial(port, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts, dsrdtr) if not self._serial.isOpen(): raise IOError("Could not open serial port") atexit.register(self.close) @property def input_history(self): """Getter for input_history.""" return self._input_history @property def clock(self): """Getter for clock.""" return self._clock @property def serial(self): """Getter for serial.""" return self._serial @property def os_buffer_size(self): """Getter for os_buffer_size.""" return self._os_buffer_size @property def baudrate(self): """Getter for baudrate.""" return self._serial.baudrate @baudrate.setter def baudrate(self, value): """Setter for baudrate.""" self._serial.baudrate = value @property def bytesize(self): """Getter for bytesize.""" return self._serial.bytesize @bytesize.setter def bytesize(self, value): """Setter for bytesize.""" self._serial.bytesize = value @property def parity(self): """Getter for parity.""" return self._serial.parity @parity.setter def parity(self, value): """Setter for parity.""" self._serial.parity = value @property def stopbits(self): """Getter for stopbits.""" return self._serial.stopbits @stopbits.setter def stopbits(self, value): """Setter for stopbits.""" self._serial.stopbits = value @property def timeout(self): """Getter for timeout.""" return self._serial.timeout @timeout.setter def timeout(self, value): """Setter for timeout.""" self._serial.timeout = value @property def xonxoff(self): """Getter for xonxoff.""" return self._serial.xonxoff @xonxoff.setter def xonxoff(self, value): """Setter for xonxoff.""" self._serial.xonxoff = value @property def rtscts(self): """Getter for rtscts.""" return self._serial.rtscts @rtscts.setter def rtscts(self, value): """Setter for rtscts.""" self._serial.rtscts = value @property def dsrdtr(self): """Getter for dsrdtr.""" return self._serial.dsrdtr @dsrdtr.setter def dsrdtr(self, value): """Setter for dsrdtr.""" self._serial.dsrdtr = value def close(self): """Close the serial port.""" try: self._serial.close() except: pass @property def has_input_history(self): """Returns if a input_history exists or not (True / False).""" return (type(self._input_history) == ByteBuffer) def clear(self, skip_input_history=False): """Clear the serial port. Notes ----- If an input_history is used, all data in the receive buffer, will be added to the history before clearing (via read_input()). Note: The copy process might take a few milliseconds. If you need a very fast clearing of the device buffer, you should skip copying the data into the input_history using the skip_input_history parameter. Parameters ---------- skip_input_history : bool, optional if True available data will not be copied to the input_history. (default = False) """ if self.has_input_history and not skip_input_history: self.read_input() else: self._serial.flushInput() if self._logging: expyriment._active_exp._event_file_log("SerialPort {0},cleared".\ format(repr(self._serial.port)), 2) def read_input(self): """Read all input from serial port. If a input_history is used, all received data will be added. Returns ------- out : list of bytes """ read_time = self._clock.time read = self._serial.read(self._serial.inWaiting()) if len(read) > 0: read = map(ord, list(read)) if self.has_input_history: if len(self._input_history.memory): last_time = self._input_history.memory[-1][1] else: last_time = 0 #first time self._input_history.add_events(read) if len(read) >= (self._os_buffer_size - 1) and last_time: warn_message = "{0} not updated for {1} ms!".format( self._input_history.name, read_time - last_time) print "Warning: " + warn_message expyriment._active_exp._event_file_warn(warn_message) if self._logging: expyriment._active_exp._event_file_log( "SerialPort {0}, read input, {1} bytes".format( repr(self._serial.port), len(read)), 2) return read return [] def poll(self): """Poll the serial port. If a input_history is used, it will be added. Returns ------- out : byte one byte only will be returned """ poll_time = self._clock.time read = self._serial.read() if read != "": if self.has_input_history: last = self._input_history.get_last_event() self._input_history.add_event(ord(read)) if last[1] > 0: # if input_history is empty if self._serial.inWaiting() >= (self._os_buffer_size - 1): warn_message = "{0} not updated for {1} ms!".format( self._input_history.name, poll_time - last[1]) print "Warning: " + warn_message expyriment._active_exp._event_file_warn(warn_message) if self._logging: expyriment._active_exp._event_file_log( "SerialPort {0},received,{1},poll".format( repr(self._serial.port), ord(read)), 2) return ord(read) return None def read_line(self, duration=None): """Read a line from serial port (until newline) and return string. Notes ----- The function is waiting for input. Use the duration parameter to avoid too long program blocking. Parameters ---------- duration : int, optional try to read for given amount of time (default=None) Returns ------- line : str """ rtn_string = "" if duration is not None: timeout_time = self._clock.time + duration if self._logging: expyriment._active_exp._event_file_log( "SerialPort {0}, read line, start".format( repr(self._serial.port)), 2) while True: expyriment._active_exp._execute_wait_callback() byte = self.poll() if byte: byte = chr(byte) if byte != '\n' and byte != '\r': rtn_string = rtn_string + byte elif byte == '\n': break elif byte == '\r': pass elif duration is not None and self._clock.time >= timeout_time: break if self._logging: expyriment._active_exp._event_file_log("SerialPort {0}, read line, end"\ .format(repr(self._serial.port)), 2) return rtn_string @staticmethod def get_available_ports(): """Return an list of strings representing the available serial ports. Notes ----- If pyserial is not installed, 'None' will be returned. Returns ------- arr : list list of strings representing the available serial ports """ import types if type(serial) is not types.ModuleType: return None ports = [] if platform.startswith("linux"): #for operation systems dev = sorted(listdir('/dev')) max_length = 0 for d in dev: if d.startswith("tty") and len(d) > max_length: max_length = len(d) for length in range(7, max_length + 1): for p in dev: if p.startswith("ttyUSB") and len(p) == length: ports.append("/dev/" + p) for length in range(5, max_length + 1): for p in dev: if p.startswith("ttyS") and len(p) == length: ports.append("/dev/" + p) elif platform == "dawin": #for MacOS dev = sorted(listdir('/dev')) max_length = 0 for d in dev: if d.startswith("cu") and len(d) > max_length: max_length = len(d) for length in range(13, max_length + 1): for p in dev: if p.startswith("cu.usbserial") and len(p) == length: ports.append("/dev/" + p) for length in range(5, max_length + 1): for p in dev: if p.startswith("cuad") and len(p) == length: ports.append("/dev/" + p) else: #for windows, os2 for p in range(256): try: s = serial.Serial(p) if s.isOpen(): ports.append(s.portstr) s.close() except serial.SerialException: pass return ports def send(self, data): """Send data via the serial port. Parameters ---------- data : int data to be send (int) """ self._serial.write(chr(data)) if self._logging: expyriment._active_exp._event_file_log("SerialPort {0},sent,{1}"\ .format(repr(self._serial.port), data), 2) @staticmethod def _self_test(exp): """Test the serial port""" def int2bin(n, count=8): return "".join([str((n >> y) & 1) for y in range(count - 1, -1, -1)]) result = {} result["testsuite_serial_port"] = "" result["testsuite_serial_baudrate"] = "" result["testsuite_serial_parity"] = "" result["testsuite_serial_stopbits"] = "" result["testsuite_serial_success"] = "No" ports = expyriment.io.SerialPort.get_available_ports() if ports is None: expyriment.stimuli.TextScreen( "The Python package 'pySerial' is not installed!", "[Press RETURN to continue]").present() exp.keyboard.wait(expyriment.misc.constants.K_RETURN) return result elif ports == []: expyriment.stimuli.TextScreen("No serial ports found!", "[Press RETURN to continue]").present() exp.keyboard.wait(expyriment.misc.constants.K_RETURN) return result else: import serial idx = expyriment.io.TextMenu("Select Serial Port", ports, width=200, justification=1, scroll_menu=5).get() comport = ports[idx] rates = [0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000] idx = expyriment.io.TextMenu("Select Baudrate", rates, width=200, justification=1, scroll_menu=2).get( preselected_item=14) baudrate = rates[idx] parities = ["N", "E", "O"] idx = expyriment.io.TextMenu("Select Parity", parities, width=200, justification=1, scroll_menu=2).get( preselected_item=0) parity = parities[idx] stopbits = [0, 1, 1.5, 2] idx = expyriment.io.TextMenu("Select Stopbits", stopbits, width=200, justification=1, scroll_menu=2).get( preselected_item=1) stopbit = stopbits[idx] expyriment.stimuli.TextScreen("Serial Port {0}".format(comport), "").present() try: ser = expyriment.io.SerialPort(port=comport, baudrate=baudrate, parity=parity, stopbits=stopbit, input_history=True) except serial.SerialException: expyriment.stimuli.TextScreen("Could not open {0}!".format(comport), "[Press RETURN to continue]").present() exp.keyboard.wait(expyriment.misc.constants.K_RETURN) result["testsuite_serial_port"] = comport result["testsuite_serial_baudrate"] = baudrate result["testsuite_serial_parity"] = parity result["testsuite_serial_stopbits"] = stopbit result["testsuite_serial_success"] = "No" return result cnt = 0 while True: read = ser.read_input() if len(read) > 0: byte = read[-1] cnt = ser.input_history.get_size() if byte is not None: expyriment.stimuli.TextScreen("Serial Port {0}".format(comport), "{0}\n {1} - {2}".format(cnt, byte, int2bin(byte))).present() key = exp.keyboard.check() if key: break result["testsuite_serial_port"] = comport result["testsuite_serial_baudrate"] = baudrate result["testsuite_serial_parity"] = parity result["testsuite_serial_stopbits"] = stopbit result["testsuite_serial_success"] = "Yes" return result python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_parallelport.py0000644000175000017500000001004612314561273025027 0ustar oliveroliver""" Input and output parallel port. This module contains a class implementing parallel port input/output. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from sys import platform from os import listdir try: import parallel except: parallel = None import expyriment from _input_output import Input, Output class ParallelPort(Input, Output): """A class implementing a parallel port input and output. Notes ----- CAUTION: Under Windows (starting from 2000) direct I/O is blocked. Install http://sourceforge.net/projects/pyserial/files/pyparallel/giveio/ """ def __init__(self): """Create a parallel port input and output. """ import types if type(parallel) is not types.ModuleType: message = """ParallelPort can not be initialized. The Python package 'pyParallel' is not installed.""" raise ImportError(message) if float(parallel.VERSION) < 0.2: raise ImportError("Expyriment {0} ".format(__version__) + "is not compatible with PyParallel {0}.".format( parallel.VERSION) + "\nPlease install PyParallel 0.2 or higher.") Input.__init__(self) Output.__init__(self) self._parallel = parallel.Parallel() self.input_history = False # dummy @property def parallel(self): """Getter for parallel""" return self._parallel @property def has_input_history(self): """Returns always False, because ParallelPort has no input history.""" return False def clear(self): """Clear the parallell port. Dummy method required for port interfaces (see e.g. ButtonBox) """ pass def poll(self): """Poll the parallel port. The parlallel port will be polled. The result will be put into the buffer and returned. The parallel module for Python can only read three of the status lines. The result is thus coded in three bits: Acknowledge Paper-Out Selected Example: '4' means only Selected is receiving data ("001"). To send out data the actual data lines are used. """ bits = "{2}{1}{0}".format(int(self._parallel.getInAcknowledge()), int(self._parallel.getInPaperOut()), int(self._parallel.getInSelected())) byte = int(bits, 2) if self._logging: expyriment._active_exp._event_file_log( "ParallelPort,received,{0},poll".format(ord(byte)), 2) return ord(byte) @staticmethod def get_available_ports(): """Return an array of strings representing the available serial ports. If pyparallel is not installed, 'None' will be returned. Returns ------- ports : list array of strings representing the available serial ports """ import types if type(parallel) is not types.ModuleType: return None ports = [] if platform.startswith("linux"): #for Linux operation systems dev = listdir('/dev') for p in dev: if p.startswith("parport"): ports.append(p) elif platform == "dawin": #for MacOS pass else: #for windows, os2 for p in range(256): try: p = parallel.Parallel(p) ports.append("LTP{0}".format(p + 1)) except: pass ports.sort() return ports def send(self, data): """Send data. Parameters ---------- data : int data to be sent """ self.parallel.setData(data) if self._logging: expyriment._active_exp._event_file_log( "ParallelPort,sent,{0}".format(data), 2) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_markeroutput.py0000644000175000017500000000672712314561273025103 0ustar oliveroliver""" A marker output. This module contains a class implementing a marker output """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults import expyriment from _input_output import Output from expyriment.misc._timer import get_time class MarkerOutput(Output): """A class implementing a marker output.""" def __init__(self, interface, default_code=None, default_duration=None): """Create a marker output. If a default code is specified, it will automatically be applied when send() is called without a code. If a default duration is specified, a 0 is sent automatically after the specifed duration when send() is called without a duration. Notes ----- EEG/MEG systems: If the system is receiving the markers on a parallel port, the duration between sending a code an the subsequent 0 should be at least 1000/samplerate! Parameters ---------- interface : io.SerialPort or io.ParallelPort interface to use default_code : int, optional default code default_duration : int, optional default duration (in ms) for sending 0 after a code """ Output.__init__(self) self._interface = interface if default_code is not None: self._default_code = default_code else: self._default_code = defaults.markeroutput_default_code if default_duration is not None: self._default_duration = default_duration else: self._default_duration = defaults.markeroutput_default_duration @property def interface(self): """Getter for interface""" return self._interface @property def default_code(self): """Getter for default_code""" return self._default_code @default_code.setter def default_code(self, value): """Getter for default_code""" self._default_code = value @property def default_duration(self): """Getter for default_duration""" return self._default_duration @default_duration.setter def default_duration(self, value): """Getter for default_duration""" self._default_duration = value def send(self, code=None, duration=None): """Send a marker. This sends a marker via the specified interface. If a duration is given, a 0 will be sent automatically after each code. Note for EEG/MEG systems: If the system is receiving the markers on a parallel port, the duration between sending a code an the subsequent 0 should be at least 1000/samplerate! Parameters ---------- code : int, optional a specific code durartion : int, optional duration (in ms) for sending a 0 after a code """ if not code: code = self.default_code if not duration: duration = self.default_duration self._interface.send(code) if duration: start = get_time() while (get_time() - start) * 1000 < duration: pass self._interface.send(0) if self._logging: expyriment._active_exp._event_file_log( "MarkerOutput,sent,{0}".format(code)) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_keyboard.py0000644000175000017500000002147212314561273024133 0ustar oliveroliver""" Keyboard input. This module contains a class implementing pygame keyboard input. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import time, sys import pygame try: import android.show_keyboard as android_show_keyboard import android.hide_keyboard as android_hide_keyboard except ImportError: android_show_keyboard = android_hide_keyboard = None import defaults import expyriment from expyriment.misc._timer import get_time from _input_output import Input quit_key = None pause_key = None end_function = None pause_function = None class Keyboard(Input): """A class implementing a keyboard input. Calling `expyriment.control.intialize(exp)` will automatically create a keyboard instance and will reference it in exp.keyboard for easy access. """ @staticmethod def process_control_keys(key_event=None): """Check if quit_key or pause_key has been pressed. Reads pygame event cue if no key_event is specified. Parameters ---------- key_event : int, optional key event to check Returns ------- out : bool True if quitting or pause screen has been displayed, False otherwise """ if key_event: if key_event.type == pygame.KEYDOWN: if key_event.key == quit_key and \ end_function is not None: confirm = end_function(confirmation=True) if confirm: sys.exit() return True elif key_event.key == pause_key and \ pause_function is not None: pause_function() return True else: for event in pygame.event.get(pygame.KEYDOWN): return Keyboard.process_control_keys(event) # recursive return False def __init__(self, default_keys=None): """Create a keyboard input. Parameters ---------- default_keys : int or list, optional a default key or list of default keys """ if default_keys is not None: self._default_keys = default_keys else: self._default_keys = defaults.keyboard_default_keys Input.__init__(self) @property def default_keys(self): """Getter for default keys""" return self._default_keys @default_keys.setter def default_keys(self, value): """Setter for default keys""" self._default_keys = value def clear(self): """Clear the event queue from keyboard events.""" pygame.event.clear(pygame.KEYDOWN) pygame.event.clear(pygame.KEYUP) if self._logging: expyriment._active_exp._event_file_log("Keyboard,cleared", 2) def read_out_buffered_keys(self): """Reads out all keydown events and clears queue.""" pygame.event.pump() pygame.event.clear(pygame.KEYUP) rtn = [] for event in pygame.event.get(pygame.KEYDOWN): Keyboard.process_control_keys(event) rtn.append(event.key) return rtn def check(self, keys=None, check_for_control_keys=True): """Check if keypress is in event queue. Parameters ---------- keys : int or list, optional a specific key or list of keys to check check_for_control_keys : bool, optional checks if control key has been pressed (default=True) Returns ------- key : int pressed key or None. Only the first occurence is returned! """ if keys is None: keys = self.default_keys if type(keys) is not list and keys is not None: keys = [keys] pygame.event.pump() pygame.event.clear(pygame.KEYUP) for event in pygame.event.get(pygame.KEYDOWN): if check_for_control_keys: Keyboard.process_control_keys(event) if keys: if event.key in keys: if self._logging: expyriment._active_exp._event_file_log( "Keyboard,received,{0},check".format(event.key)) return event.key else: if self._logging: expyriment._active_exp._event_file_log( "Keyboard,received,{0},check".format(event.key), 2) return event.key return None def wait(self, keys=None, duration=None, wait_for_keyup=False, check_for_control_keys=True): """Wait for keypress(es) (optionally for a certain amount of time). This function will wait for a keypress and returns the found key as well as the reaction time. (This function clears the event queue!) Parameters ---------- keys : int or list, optional a specific key or list of keys to wait for duration : int, optional maximal time to wait in ms wait_for_keyup : bool, optional if True it waits for key-up check_for_control_keys : bool, optional checks if control key has been pressed (default=True) Returns ------- found : char pressed charater rt : int reaction time in ms """ if android_show_keyboard is not None: android_show_keyboard() start = get_time() rt = None found_key = None self.clear() if keys is None: keys = self.default_keys if keys is not None and type(keys) is not list: keys = [keys] if wait_for_keyup: target_event = pygame.KEYUP else: target_event = pygame.KEYDOWN pygame.event.pump() done = False while not done: expyriment._active_exp._execute_wait_callback() for event in pygame.event.get(): if check_for_control_keys and Keyboard.process_control_keys(event): done = True elif event.type == target_event: if keys is not None: if event.key in keys: rt = int((get_time() - start) * 1000) found_key = event.key done = True else: rt = int((get_time() - start) * 1000) found_key = event.key done = True if duration and not done: done = int((get_time() - start) * 1000) >= duration time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log("Keyboard,received,{0},wait"\ .format(found_key)) if android_hide_keyboard is not None: android_hide_keyboard() return found_key, rt def wait_char(self, char, duration=None, check_for_control_keys=True): """Wait for character(s) (optionally for a certain amount of time). This function will wait for one or more characters and returns the found character as well as the reaction time. (This function clears the event queue!) Parameters ---------- char : int or list a specific character or list of characters to wait for duration : int, optional maximal time to wait in ms check_for_control_keys : bool, optional checks if control key has been pressed (default=True) Returns ------- found : char pressed charater rt : int reaction time in ms """ start = get_time() rt = None found_char = None self.clear() if type(char) is not list: char = [char] pygame.event.pump() done = False while not done: expyriment._active_exp._execute_wait_callback() for event in pygame.event.get(): if check_for_control_keys and Keyboard.process_control_keys(event): done = True elif event.type == pygame.KEYDOWN: if event.unicode in char: rt = int((get_time() - start) * 1000) found_char = event.unicode done = True if duration and not done: done = int((get_time() - start) * 1000) >= duration time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log( "Keyboard,received,{0},wait_char".format(found_char)) return found_char, rt python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_mouse.py0000644000175000017500000003737412314561273023473 0ustar oliveroliver""" Mouse input. This module contains a class implementing pygame mouse input. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import pygame import defaults import expyriment from expyriment.misc._timer import get_time from _keyboard import Keyboard from _input_output import Input class Mouse(Input): """A class implementing a mouse input. Calling `expyriment.control.intialize(exp)` will automatically create a mouse instance and will reference it in exp.mouse for easy access. """ def __init__(self, show_cursor=True, track_button_events=None, track_motion_events=None): """Initialize a mouse input. Notes ----- It is strongly suggest to avoid tracking of motions events, (track_motion_events=True), because it quickly causes an overflow in the Pygame event queue and you might consequently loose important events. Parameters ---------- show_cursor : bool, optional shows mouse cursor (default = True) track_button_events : bool, optional track button events via Pygame queue (default = True) track_motion_events : bool, optional track motion events via Pygame queue (default = False) """ Input.__init__(self) if show_cursor is None: show_cursor = defaults.mouse_show_cursor if track_button_events is None: track_button_events = defaults.mouse_track_button_events if track_motion_events is None: track_motion_events = defaults.mouse_track_motion_events if show_cursor: self.show_cursor(track_button_events, track_motion_events) else: self.track_button_events = track_button_events self.track_motion_events = track_motion_events @property def track_button_events(self): """Getter for track_button_events.""" return self._track_button_events @track_button_events.setter def track_button_events(self, value): """Setter for track_button_events. Switch on/off the processing of button and wheel events. """ self._track_button_events = value if value: pygame.event.set_allowed(pygame.MOUSEBUTTONDOWN) pygame.event.set_allowed(pygame.MOUSEBUTTONUP) else: pygame.event.set_blocked(pygame.MOUSEBUTTONDOWN) pygame.event.set_blocked(pygame.MOUSEBUTTONUP) @property def track_motion_events(self): """Getter for track_motion_events. Switch on/off the buffering of motion events in the Pygame event queue. Notes ----- It is strongly suggest to avoid tracking of motions events, (track_motion_events=True), because it quickly causes an overflow in the Pygame event queue and you might consequently loose important events. """ return self._track_motion_events @track_motion_events.setter def track_motion_events(self, value): """Setter for track_motion_events. Switch on/off the processing of motion events. """ self._track_motion_events = value if value: pygame.event.set_allowed(pygame.MOUSEMOTION) else: pygame.event.set_blocked(pygame.MOUSEMOTION) @property def pressed_buttons(self): """Getter for pressed buttons.""" pygame.event.pump() return pygame.mouse.get_pressed() @property def is_cursor_visible(self): """returns if cursor is visible""" visible = pygame.mouse.set_visible(False) pygame.mouse.set_visible(visible) return visible def get_last_button_down_event(self): """Get the last button down event. Returns ------- btn_id : int button number (0,1,2) or 3 for wheel up or 4 for wheel down """ rtn = None pygame.event.clear(pygame.MOUSEBUTTONUP) pygame.event.clear(pygame.MOUSEMOTION) for event in pygame.event.get(pygame.MOUSEBUTTONDOWN): if event.button > 0: rtn = event.button - 1 return rtn def get_last_button_up_event(self): """Get the last button up event. Returns ------- btn_id : int button number (0,1,2) """ rtn = None pygame.event.clear(pygame.MOUSEBUTTONDOWN) pygame.event.clear(pygame.MOUSEMOTION) for event in pygame.event.get(pygame.MOUSEBUTTONUP): if event.button > 0: rtn = event.button - 1 return rtn def check_button_pressed(self, button_number): """Return (True/False) if a specific button is currently pressed. Returns ------- btn_id : int button number (0,1,2) """ btns = self.pressed_buttons if len(btns) >= 1 and button_number >= 0: return btns[button_number] else: return False def check_wheel(self): """Check the mouse wheel. Returns ------- direction : str "up" or "down" if mouse wheel has been recently rotated upwards or downwards otherwise it returns None. """ evt = self.get_last_button_down_event() if evt == 3: return "up" elif evt == 4: return "down" else: return None @property def position(self): """Getter of mouse position.""" pygame.event.pump() screen_size = expyriment._active_exp.screen.surface.get_size() pos = pygame.mouse.get_pos() return (pos[0] - screen_size[0] / 2, -pos[1] + screen_size[1] / 2) @position.setter def position(self, position): """Setter for mouse position.""" screen_size = expyriment._active_exp.screen.surface.get_size() pos = (position[0] + screen_size[0] / 2, - position[1] + screen_size[1] / 2) pygame.mouse.set_pos(pos) def set_cursor(self, size, hotspot, xormasks, andmasks): """Set the cursor. Parameters ---------- size : (int, int) size of the cursor hotspot : (int, int) position of the hotspot (0,0 is top left) xormask : list sequence of bytes with cursor xor data masks andmask : list sequence of bytes with cursor bitmask data """ return pygame.mouse.set_cursor(size, hotspot, xormasks, andmasks) def get_cursor(self): """Get the cursor.""" return pygame.mouse.get_cursor() def clear(self): """Clear the event cue from mouse events.""" pygame.event.clear(pygame.MOUSEBUTTONDOWN) pygame.event.clear(pygame.MOUSEBUTTONUP) pygame.event.clear(pygame.MOUSEMOTION) if self._logging: expyriment._active_exp._event_file_log("Mouse,cleared", 2) def wait_event(self, wait_button=True, wait_motion=True, buttons=None, duration=None, wait_for_buttonup=False): """Wait for a mouse event (i.e., motion, button press or wheel event) Parameters ---------- wait_button : bool, optional set 'False' to ignore for a button presses (default=True) wait_motion : bool, optional set 'False' to ignore for a mouse motions (default=True) buttons : int or list, optional a specific button or list of buttons to wait for duration : int, optional the maximal time to wait in ms wait_for_buttonup : bool, optional if True it waits for button-up default=False) Returns ------- event_id : int id of the event that quited waiting move : bool True if a motion occured pos : (int, int) mouse position (tuple) rt : int reaction time Notes ------ button id coding - None for no mouse button event or - 0,1,2 for left. middle and right button or - 3 for wheel up or - 4 for wheel down (wheel works only for keydown events). """ start = get_time() self.clear() old_pos = pygame.mouse.get_pos() btn_id = None rt = None motion_occured = False if buttons is None: buttons = [0, 1, 2, 3, 4] if type(buttons) is not list: buttons = [buttons] while True: expyriment._active_exp._execute_wait_callback() if wait_motion: motion_occured = old_pos != pygame.mouse.get_pos() if wait_button: if wait_for_buttonup: btn_id = self.get_last_button_up_event() else: btn_id = self.get_last_button_down_event() if btn_id in buttons or motion_occured: rt = int((get_time() - start) * 1000) break elif Keyboard.process_control_keys() or (duration is not None and \ int((get_time() - start) * 1000) >= duration): break position_in_expy_coordinates = self.position if self._logging: expyriment._active_exp._event_file_log( "Mouse,received,{0}-{1},wait_event".format(btn_id, motion_occured)) return btn_id, motion_occured, position_in_expy_coordinates, rt def wait_press(self, buttons=None, duration=None, wait_for_buttonup=False): """Wait for a mouse button press or mouse wheel event. Parameters ---------- buttons : int or list, optional a specific button or list of buttons to wait for duration : int, optional maximal time to wait in ms wait_for_buttonup : bool, optional if True it waits for button-up Returns ------- event_id : int id of the event that quited waiting pos : (int, int) mouse position (tuple) rt : int reaction time Notes ------ button id coding - None for no mouse button event or - 0,1,2 for left. middle and right button or - 3 for wheel up or - 4 for wheel down (wheel works only for keydown events). """ rtn = self.wait_event(wait_button=True, wait_motion=False, buttons=buttons, duration=duration, wait_for_buttonup=wait_for_buttonup) return rtn[0], rtn[2], rtn[3] def wait_motion(self, duration=None): """Wait for a mouse motion. Parameters ---------- duration : int, optional maximal time to wait in ms Returns ------- pos : (int, int) mouse position (tuple) rt : int reaction time """ rtn = self.wait_event(wait_button=False, wait_motion=True, buttons=[], duration=duration, wait_for_buttonup=False) return rtn[2], rtn[3] def show_cursor(self, track_button_events=True, track_motion_events=False): """Show the cursor. Parameters ---------- track_button_events : bool, optional tracking button events (default = True) track_motion_events : bool, optional tracking motion events (default = False) """ pygame.mouse.set_visible(True) self.track_button_events = track_button_events self.track_motion_events = track_motion_events def hide_cursor(self, track_button_events=False, track_motion_events=False): """Hide the cursor. Parameters ---------- track_button_events : bool, optional tracking button events (default = True) track_motion_events : bool, optional tracking motion events (default = False) """ pygame.mouse.set_visible(False) self.track_button_events = track_button_events self.track_motion_events = track_motion_events @staticmethod def _self_test(exp): """Test the mouse. Returns ------- polling_time : int polling time buttons_work : int 1 -- if mouse test was ended with mouse button, 0 -- if testing has been quit with q """ # measure mouse polling time info = """This will test how timing accurate your mouse is. [Press RETURN to continue]""" expyriment.stimuli.TextScreen("Mouse test (1)", info).present() exp.keyboard.wait(expyriment.misc.constants.K_RETURN) mouse = expyriment.io.Mouse() go = expyriment.stimuli.TextLine("Keep on moving...") go.preload() expyriment.stimuli.TextLine("Please move the mouse").present() mouse.wait_motion() go.present() exp.clock.reset_stopwatch() motion = [] while exp.clock.stopwatch_time < 200: _pos, rt = mouse.wait_motion() motion.append(rt) expyriment.stimuli.TextLine("Thanks").present() polling_time = expyriment.misc.statistics.mode(motion) info = """Your mouse polling time is {0} ms. [Press RETURN to continue] """.format(polling_time) text = expyriment.stimuli.TextScreen("Results", info) text.present() exp.keyboard.wait([expyriment.misc.constants.K_RETURN]) info = """This will test if you mouse buttons work. Please press all buttons one after the other to see if the corresponding buttons on the screen light up. When done, click inside one of the buttons on the screen to end the test. If your mouse buttons do not work, you can quit by pressing q. [Press RETURN to continue]""" expyriment.stimuli.TextScreen("Mouse test (2)", info).present() exp.keyboard.wait(expyriment.misc.constants.K_RETURN) # test mouse clicking rects = [expyriment.stimuli.Rectangle(size=[30, 30], position=[-50, 0]), expyriment.stimuli.Rectangle(size=[30, 30], position=[0, 0]), expyriment.stimuli.Rectangle(size=[30, 30], position=[50, 0])] canvas = expyriment.stimuli.Canvas(size=[350, 500]) btn = None go_on = True while go_on: canvas.clear_surface() for cnt, r in enumerate(rects): r.unload() if cnt == btn: r.colour = expyriment.misc.constants.C_YELLOW else: r.colour = expyriment.misc.constants.C_RED r.plot(canvas) if btn == 3: text = "Mouse wheel UP" elif btn == 4: text = "Mouse wheel DOWN" else: text = "" expyriment.stimuli.TextLine(text, position=[0, 50]).plot(canvas) canvas.present() btn = None while btn is None: btn = mouse.get_last_button_down_event() if btn is not None: position = mouse.position for r in rects: if r.overlapping_with_position(position): buttons_work = 1 mouse.hide_cursor() go_on = False break elif exp.keyboard.check(keys=expyriment.misc.constants.K_q): buttons_work = 0 mouse.hide_cursor() go_on = False break result = {} result["testsuite_mouse_polling_time"] = str(polling_time) + " ms" result["testsuite_mouse_buttons_work"] = buttons_work return result python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_textmenu.py0000644000175000017500000003223412314561273024202 0ustar oliveroliver""" A TextMenu. This module contains a class implementing a TextMenu. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults import expyriment from _keyboard import Keyboard from _input_output import Input class TextMenu(Input): """A class implementing a text menu.""" def __init__(self, heading, menu_items, width, position=None, text_size=None, gap=None, heading_font=None, text_font=None, background_colour=None, text_colour=None, heading_text_colour=None, select_background_colour=None, select_text_colour=None, select_frame_colour=None, select_frame_line_width=None, justification=None, scroll_menu=None, background_stimulus=None, mouse=None): """Create a text menu. This creates a menu with items to be selected. Parameters ---------- heading : str menu heading menu_items : str or list list with menu items width : int width of the menu position : (int, int), optional text_size : int, optional background_colour : (int, int, int), optional background colour of the menu gap : int, optional size of gap (pixel) between heading and item list heading_font : str, optional font to be used for the heading text_font : str, optional font to be used for the text text_colour : (int, int, int), optional text colour of the items heading_text_colour : (int, int, int), optional text colour of the heading select_background_colour : (int, int, int), optional background colour of the currently selected item select_text_colour : (int, int, int), optional text colour of the currently selected item select_frame_colour : (int, int, int), optional colour of the frame around the selected item select_frame_line_width : int, optional line width of the frame around the selected item justification : int, optional text justification: 0 (left), 1 (center), 2 (right) scroll_menu : int, optional maximum length of a item list before a scroll menu will be display. If the parameter is 0 of False scroll menu will not be displayed background_stimulus : visual expyriment stimulus, optional The background stimulus is a second stimulus that will be presented together with the TextMenu. For both stimuli overlap TextMenu will appear on top of the background_stimulus mouse : expyriment.io.Mouse object, optional If a mouse object is given, the menu can also be controlled by the mouse """ if position is None: position = defaults.textmenu_position if gap is None: gap = defaults.textmenu_gap if heading_font is None: heading_font = defaults.textmenu_text_font if text_font is None: text_font = defaults.textmenu_text_font if text_size is None: text_size = defaults.textmenu_text_size if background_colour is None: background_colour = defaults.textmenu_background_colour if text_colour is None: text_colour = defaults.textmenu_text_colour if heading_text_colour is None: heading_text_colour = defaults.textmenu_heading_text_colour if select_background_colour is None: select_background_colour = \ defaults.textmenu_select_background_colour if select_text_colour is None: select_text_colour = defaults.textmenu_select_text_colour if select_frame_colour is None: select_frame_colour = defaults.textmenu_select_frame_colour if select_frame_line_width is None: select_frame_line_width = defaults.textmenu_select_frame_line_width if justification is None: justification = defaults.textmenu_justification if scroll_menu is None: scroll_menu = defaults.textmenu_scroll_menu self._scroll_menu = abs(int(scroll_menu)) if self._scroll_menu > 0 and self._scroll_menu < 5: self._scroll_menu = 5 self._gap = gap self._position = position self._bkg_colours = [background_colour, select_background_colour] self._text_colours = [text_colour, select_text_colour] self._line_size = (width, expyriment.stimuli.TextLine( menu_items[0], text_size=text_size).surface_size[1] + 2) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._frame = expyriment.stimuli.Frame( frame_line_width=select_frame_line_width, size=(self._line_size[0] + 2 * select_frame_line_width, self._line_size[1] + 2 * select_frame_line_width), colour=select_frame_colour) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 if background_stimulus is not None: if background_stimulus.__class__.__base__ in \ [expyriment.stimuli._visual.Visual, expyriment.stimuli.Shape]: self._background_stimulus = background_stimulus else: raise TypeError("{0} ".format(type(background_stimulus)) + "is not a valid background stimulus. " + "Use an expyriment visual stimulus.") else: self._background_stimulus = None if mouse is not None: self._mouse = mouse else: self._mouse = None self._canvas = expyriment.stimuli.BlankScreen() expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._original_items = menu_items self._menu_items = [] for item in menu_items: self._menu_items.append(expyriment.stimuli.TextBox( "{0}".format(item), text_size=text_size, text_font=text_font, text_justification=justification, size=self._line_size)) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 self._heading = expyriment.stimuli.TextBox( heading, text_size=text_size, text_justification=justification, text_font=heading_font, text_colour=heading_text_colour, text_bold=True, background_colour=self._bkg_colours[0], size=self._line_size) expyriment.stimuli._stimulus.Stimulus._id_counter -= 1 @property def heading(self): """Getter for heading""" return self._heading @property def menu_items(self): """Getter for menu_items""" return self._menu_items @property def position(self): """Getter for position""" return self._position @property def text_size(self): """Getter for text_size""" return self._heading.text_size @property def background_colour(self): """Getter for background_colour""" return self._bkg_colours[0] @property def select_background_colour(self): """Getter for select_background_colour""" return self._bkg_colours[1] @property def gap(self): """Getter for gap""" return self._gap @property def text_colour(self): """Getter for text_colour""" return self._text_colours[0] @property def select_text_colour(self): """Getter for select_text_colour""" return self._text_colours[1] @property def heading_text_colour(self): """Getter for heading_text_colour""" return self._heading.text_colour @property def select_frame_colour(self): """Getter for select_frame_colour""" return self._frame.colour @property def select_frame_line_width(self): """Getter for select_frame_line_width""" return self._frame.line_width @property def justification(self): """Getter for justification""" return self._heading.text_justification @property def scroll_menu(self): """Getter for scroll_menu""" return self._scroll_menu @property def background_stimulus(self): """Getter for background_stimulus""" return self._background_stimulus def _append_item(self, item, is_selected, y_position): """helper function""" item.clear_surface() item.position = (self._position[0], y_position + self._position[1]) if is_selected: item.background_colour = self._bkg_colours[1] item.text_colour = self._text_colours[1] else: item.background_colour = self._bkg_colours[0] item.text_colour = self._text_colours[0] item.plot(self._canvas) def _redraw(self, selected_item): """helper function""" if self._scroll_menu > 0: n = self._scroll_menu else: n = len(self._menu_items) self._canvas.clear_surface() if self._background_stimulus is not None: self._background_stimulus.plot(self._canvas) y_pos = int(((1.5 + n) * self._line_size[1]) + (n * self._gap)) / 2 self._heading.position = (self._position[0], y_pos + self._position[1]) self._heading.plot(self._canvas) y_pos = y_pos - int(0.5 * self._line_size[1]) if self._scroll_menu == 0: for cnt, item in enumerate(self._menu_items): y_pos -= (self._line_size[1] + self._gap) self._append_item(item, cnt == selected_item, y_pos) if cnt == selected_item: self._frame.position = (0, y_pos) else: # scroll menu for cnt in range(selected_item - self._scroll_menu / 2, selected_item + 1 + self._scroll_menu / 2): y_pos -= (self._line_size[1] + self._gap) if cnt >= 0 and cnt < len(self._menu_items): self._append_item(self._menu_items[cnt], cnt == selected_item, y_pos) if cnt == selected_item: self._frame.position = (0, y_pos) if self._frame.line_width > 0: self._frame.plot(self._canvas) self._canvas.present() def get(self, preselected_item=0): """Present the menu and return the selected item. Parameters ---------- preselected_item : int, optional item that is preselected when showing menu Returns ------- selected : int integer representing the selected item in the list """ selected = preselected_item # Keyboard if self._mouse is None: while True: self._redraw(selected) key = Keyboard().wait()[0] if key == expyriment.misc.constants.K_UP: selected -= 1 elif key == expyriment.misc.constants.K_DOWN: selected += 1 elif key in expyriment.misc.constants.K_ALL_DIGITS and\ key > expyriment.misc.constants.K_0: selected = key - expyriment.misc.constants.K_1 elif key == expyriment.misc.constants.K_RETURN: break if selected < 0: selected = 0 elif selected >= len(self._menu_items): selected = len(self._menu_items) - 1 return selected # Mouse else: pressed = None while True: pressed = None self._redraw(selected) event, pos, rt = self._mouse.wait_press() if self._scroll_menu == 0: for cnt in range(len(self._menu_items)): if 0 <= cnt < len(self._menu_items): if self._menu_items[cnt].overlapping_with_position(pos): pressed = cnt else: for cnt in range(selected-self._scroll_menu/2, selected+1+self._scroll_menu/2): if 0 <= cnt < len(self._menu_items): if self._menu_items[cnt].overlapping_with_position(pos): pressed = cnt if pressed is not None: if pressed == selected: break else: selected = pressed return self._original_items[pressed] if __name__ == "__main__": from expyriment import control control.set_develop_mode(True) defaults.event_logging = 0 exp = control.initialize() menu = TextMenu(heading="Expyriment TextMenu", items=["Items 1", "Items 1", "Items 3", "Items 4", "Items 5"], width=250) print menu.get() python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_files.py0000644000175000017500000005001712314561273023432 0ustar oliveroliver""" File input and output. This module contains classes implementing file input and output. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import atexit import os try: import locale except ImportError: locale = None # Does not exist on Android import codecs import re import types import sys import uuid import time from time import strftime from platform import uname import defaults import expyriment from expyriment.misc._timer import get_time from expyriment.misc import unicode2str, str2unicode from _input_output import Input, Output class InputFile(Input): """A class implementing an input file.""" def __init__(self, filename, encoding=None): """Create an input file. All lines in the specified text file will be read into a list of strings. Parameters ---------- filename : str, optional name of the input file encoding : str, optional the encoding used to read the content of the file """ self._filename = filename self._current_line = 1 self._lines = [] if not(os.path.isfile(self._filename)): raise IOError("The input file '{0}' does not exist.".format( unicode2str(self._filename))) if encoding is None: with open(filename, 'r') as fl: first_line = fl.readline() encoding = re.findall("coding[:=]\s*([-\w.]+)", first_line) if encoding == []: second_line = fl.readline() encoding = re.findall("coding[:=]\s*([-\w.]+)", second_line) if encoding == []: encoding = [None] else: encoding = [encoding] with codecs.open(self._filename, 'rb', encoding[0], errors='replace') as f: for line in f: self._lines.append(str2unicode(line.rstrip('\r\n'))) @property def filename(self): """Getter for filename.""" return self._filename @property def current_line(self): """Getter for current_line.""" return self._current_line @property def n_lines(self): """Getter for n_lines.""" return len(self._lines) @property def lines(self): """Getter for lines.""" return self._lines def get_line(self, line=None): """Get a specific line. If no line is given, the current line will be returned and the value of current_line will be increased by one. First line is line 1. Parameters ---------- line : int, optional number of the line to get Returns ------- line : str line as string or None if line does not exist """ if line is not None and line < 1 or line > len(self._lines): return None if line is not None: return self._lines[line - 1] else: current_line = self._current_line if current_line != len(self._lines): self._current_line += 1 return self._lines[current_line - 1] class OutputFile(Output): """A class implementing an output file.""" def __init__(self, suffix, directory, comment_char=None, time_stamp=None): """Create an output file. Filename: {MAINFILE_NAME}_{SUBJECT_ID}_{TIME_STAMP}{suffix} Parameters ---------- suffix : str file suffix/extension (str) directory : str create file in given directory comment_char : str, optional comment character time_stamp : bool, optional using time stamps, based on the experiment start time, not the current time """ Output.__init__(self) self._suffix = suffix self._directory = directory if comment_char is not None: self._comment_char = comment_char else: self._comment_char = defaults.outputfile_comment_char if time_stamp is not None: self._time_stamp = time_stamp else: self._time_stamp = defaults.outputfile_time_stamp self._buffer = [] if not os.path.isdir(directory): os.mkdir(directory) self._filename = self.standard_file_name self._fullpath = directory + "/{0}".format(self._filename) atexit.register(self.save) # Create new file fl = open(self._fullpath, 'w+') fl.close() try: locale_enc = locale.getdefaultlocale()[1] except: locale_enc = "UTF-8" self.write_comment("Expyriment {0}, {1}-file, coding: {2}".format( expyriment.get_version(), self._suffix, locale_enc)) if expyriment._active_exp.is_initialized: self.write_comment("date: {0}".format(time.strftime( "%a %b %d %Y %H:%M:%S", expyriment._active_exp.clock.init_localtime))) @property def fullpath(self): """Getter for fullpath""" return self._fullpath @property def filename(self): """Getter for filename""" return self._filename @property def directory(self): """Getter for directory""" return self._directory @property def suffix(self): """Getter for directory""" return self._suffix @property def comment_char(self): """Getter for comment_char""" return self._comment_char @property def standard_file_name(self): """Getter for the standard expyriment outputfile name. Filename: {MAINFILE_NAME}_{SUBJECT_ID}_{TIME_STAMP}{suffix} """ rtn = os.path.split(sys.argv[0])[1].replace(".py", "") if expyriment._active_exp.is_started: rtn = rtn + '_' + repr(expyriment._active_exp.subject).zfill(2) if self._time_stamp: rtn = rtn + '_' + strftime( "%Y%m%d%H%M", expyriment._active_exp.clock.init_localtime) return rtn + self.suffix def save(self): """Save file to disk.""" start = get_time() if self._buffer != []: with open(self._fullpath, 'a') as f: f.write("".join(self._buffer)) self._buffer = [] return int((get_time() - start) * 1000) def write(self, content): """Write to file. Parameters ---------- content : str content to be written (anything, will be casted to str) """ if type(content) is unicode: self._buffer.append(unicode2str(content)) else: self._buffer.append(str(content)) def write_line(self, content): """Write a text line to files. Parameters ---------- content : str content to be written (anything, will be casted to str) """ self.write(content) self.write(unicode2str(defaults.outputfile_eol)) def write_list(self, list_): """Write a list in a row. Data are separated by a delimiter. Parameters ---------- list_ : list list to be written """ for elem in list: self.write(elem) self.write(',') self.write(unicode2str(defaults.outputfile_eol)) # self.write_line(repr(list_)[1:-1].replace(" ", "")) def write_comment(self, comment): """Write a comment line to files. (i.e., text is proceeded by comment char). Parameters ---------- comment : str comment to be written (anything, will be casted to str) """ self.write(unicode2str(self.comment_char)) self.write_line(comment) def rename(self, new_filename): """Renames the output file.""" self.save() new_fullpath = self.directory + "/{0}".format(new_filename) os.rename(self._fullpath, new_fullpath) self._filename = new_filename self._fullpath = new_fullpath class DataFile(OutputFile): """A class implementing a data file.""" _file_suffix = ".xpd" def __init__(self, additional_suffix, directory=None, delimiter=None, time_stamp=None): """Create a data file. Filename: {MAINFILE_NAME}_{SUBJECT_ID}_{TIME_STAMP}{ADD_SUFFIX}.xpd Parameters ---------- additional_suffix : str additional suffix directory : str, optional directory of the file delimiter : str, optional symbol between variables time_stamp : bool, optional using time stamps, based on the experiment start time, not the current time """ if expyriment._active_exp.is_initialized: self._subject = expyriment._active_exp.subject else: self._subject = None if directory is None: directory = defaults.datafile_directory if additional_suffix is None: additional_suffix = '' if len(additional_suffix) > 0: suffix = ".{0}{1}".format(additional_suffix, self._file_suffix) else: suffix = self._file_suffix OutputFile.__init__(self, suffix, directory, time_stamp=time_stamp) if delimiter is not None: self._delimiter = delimiter else: self._delimiter = defaults.datafile_delimiter self._subject_info = [] self._experiment_info = [] self._variable_names = [] self.write_comment("--EXPERIMENT INFO") self.write_comment("e mainfile: {0}".format(os.path.split( sys.argv[0])[1])) self.write_comment("e sha1: {0}".format( expyriment.get_experiment_secure_hash())) self.write_comment("--SUBJECT INFO") self.write_comment("s id: {0}".format(self._subject)) self.write_line(self.variable_names) self._variable_names_changed = False self.save() @property def delimiter(self): """Getter for delimiter""" return self._delimiter @staticmethod def _typecheck_and_cast2str(data): """Check if data are string or numeric and cast to string""" if data is None: data = "None" if isinstance(data, types.UnicodeType): return unicode2str(data) elif type(data) in [types.StringType, types.IntType, types.LongType, types.FloatType, types.BooleanType]: return str(data) else: message = "Data to be added must to be " + \ "booleans, strings, numerics (i.e. floats or integers) " + \ "or None.\n {0} is not allowed.".format(type(data)) raise TypeError(message) def add(self, data): """Add data. Parameters ---------- data : string or numeric or list data to be added """ self.write(str(self._subject) + self.delimiter) if type(data) is list or type(data) is tuple: line = "" for counter, elem in enumerate(data): if counter > 0: line = line + self.delimiter line = line + DataFile._typecheck_and_cast2str(elem) self.write_line(line) else: self.write_line(DataFile._typecheck_and_cast2str(data)) def add_subject_info(self, text): """Adds a text the subject info header. Subject information can be extracted afterwards using misc.data_preprocessing.read_data_file. To defined between subject variables use a syntax like this: "gender = female" or "handedness : left" Parameters ---------- text : str subject infomation to be add to the file header Notes ----- The next data.save() might take longer! """ self._subject_info.append("{0}s {1}{2}".format( unicode2str(self.comment_char), unicode2str(text), unicode2str(defaults.outputfile_eol))) def add_experiment_info(self, text): """Adds a text the subject info header. Notes ----- The next data.save() might take longer! """ if isinstance(text, types.UnicodeType): text = "{0}".format(unicode2str(text)) elif type(text) is not str: text = "{0}".format(text) for line in text.splitlines(): self._experiment_info.append("{0}e {1}{2}".format( unicode2str(self.comment_char), line, unicode2str(defaults.outputfile_eol))) @property def variable_names(self): """Getter for variable_names.""" vn = self.delimiter.join(self._variable_names) return u"subject_id,{0}".format(vn) def clear_variable_names(self): """Remove all variable names from data file. Notes ----- The next data.save() might take longer! """ self._variable_names = [] self._variable_names_changed = True def add_variable_names(self, variable_names): """Add data variable names to the data file. Notes ----- The next data.save() might take longer! Parameters ---------- variables : str or list of str variable names """ if variable_names is None: return if type(variable_names) is not list: variable_names = [variable_names] self._variable_names.extend(variable_names) self._variable_names_changed = True def save(self): """Save the new data to data-file. Returns ------- time : int the time it took to execute this method """ start = get_time() if len(self._subject_info) > 0 or len(self._experiment_info) > 0 \ or self._variable_names_changed: # Re-write header and varnames tmpfile_name = "{0}/{1}".format(self.directory, uuid.uuid4()) os.rename(self._fullpath, tmpfile_name) fl = open(self._fullpath, 'w+') tmpfl = open(tmpfile_name, 'r') section = None while True: line = tmpfl.readline() if not line: break if line.startswith(self.comment_char + "e"): section = "e" elif line.startswith(self.comment_char + "s"): section = "s" else: if section == "e": # Previous line was last #e if len(self._experiment_info) > 0: fl.write("".join(self._experiment_info)) self._experiment_info = [] section = None elif section == "s": # Previous line was last #s if len(self._subject_info) > 0: fl.write("".join(self._subject_info)) self._subject_info = [] section = None # Re-write variable names after #s-section fl.write(unicode2str( self.variable_names + defaults.outputfile_eol)) self._variable_names_changed = False line = '' # Skip old varnames fl.write(line) tmpfl.close() fl.close() os.remove(tmpfile_name) self._subject_info = [] self._experiment_info = [] if self._buffer != []: OutputFile.save(self) if self._logging: expyriment._active_exp._event_file_log("Data,saved") return int((get_time() - start) * 1000) @staticmethod def get_next_subject_number(): """Return the next subject number based on the existing data files.""" subject_number = 1 if os.path.isdir(defaults.datafile_directory): mainfile_name = os.path.split(sys.argv[0])[1].replace(".py", "") for filename in os.listdir(defaults.datafile_directory): if filename.startswith(mainfile_name) and \ filename.endswith(DataFile._file_suffix): tmp = filename.replace(mainfile_name, "") tmp = tmp.replace(DataFile._file_suffix, "") tmp = tmp.split('_') try: num = int(tmp[1]) if num >= subject_number: subject_number = num + 1 except: pass return subject_number class EventFile(OutputFile): """A class implementing an event file.""" _file_suffix = ".xpe" def __init__(self, additional_suffix, directory=None, delimiter=None, clock=None, time_stamp=None): """Create an event file. Filename: {MAINFILE_NAME}_{SUBJECT_ID}_{TIME_STAMP}{ADD_SUFFIX}.xpd Parameters ---------- additional_suffix : str additional suffix directory : str, optional directory of the file delimiter : str, optional symbol between timestamp and event clock : expyriment.Clock, optional an experimental clock time_stamp : bool, optional using time stamps, based on the experiment start time, not the current time """ if directory is None: directory = defaults.eventfile_directory if additional_suffix is None: additional_suffix = '' if len(additional_suffix) > 0: suffix = ".{0}{1}".format(additional_suffix, self._file_suffix) else: suffix = self._file_suffix OutputFile.__init__(self, suffix, directory, time_stamp=time_stamp) if delimiter is not None: self._delimiter = delimiter else: self._delimiter = defaults.eventfile_delimiter if clock is not None: self._clock = clock else: if not expyriment._active_exp.is_initialized: raise RuntimeError( "Cannot find a clock. Initialize Expyriment!") self._clock = expyriment._active_exp.clock try: display = repr(expyriment._active_exp.screen.window_size) window_mode = repr(expyriment._active_exp.screen.window_mode) opengl = repr(expyriment._active_exp.screen.open_gl) except: display = "unknown" window_mode = "unknown" opengl = "unknown" self.write_comment("sha1: {0}".format( expyriment.get_experiment_secure_hash())) self.write_comment("display: size={0}, window_mode={1}, opengl={2}" .format(display, window_mode, opengl)) self.write_comment("os: {0}".format(uname())) self.write_line("Time,Type,Event,Value,Detail,Detail2") self.save() @property def clock(self): """Getter for clock""" return self._clock @property def delimiter(self): """Getter for delimiter""" return self._delimiter def log(self, event): """Log an event. Parameters ---------- event : anything the event to be logged (anything, will be casted to str) """ if isinstance(event, types.UnicodeType): event = unicode2str(event) line = repr(self._clock.time) + self.delimiter + str(event) self.write_line(line) def warn(self, message): """Log a warning message. Parameters ---------- message : str warning message to log """ line = "WARNING: " + message self.write_line(line) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_triggerinput.py0000644000175000017500000001210512314561273025047 0ustar oliveroliver""" A Trigger input This module contains a class implementing a trigger input. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults import expyriment from expyriment.misc import compare_codes from expyriment.misc._timer import get_time from _keyboard import Keyboard from _input_output import Input class TriggerInput(Input): """A class implementing a trigger input.""" def __init__(self, interface, default_code=None): """Create a trigger input. Parameters interface -- the interface to use (expyrment.io.SerialPort or expyriment.io.ParallelPort object) default_code -- the default code of the trigger (int) (optional) """ Input.__init__(self) self._interface = interface if default_code is not None: self._default_code = default_code else: self._default_code = defaults.triggerinput_default_code @property def interface(self): """Getter for interface""" return self._interface @property def default_code(self): """Getter for default_code""" return self._default_code @default_code.setter def default_code(self, value): """Getter for default_code""" self._default_code = value def wait(self, code=None, bitwise_comparison=False): """Wait for a trigger. Returns the code received and the reaction time [code, rt]. If bitwise_comparison = True, the function performs a bitwise comparison (logical and) between code and received input and waits until a certain bit pattern is set. Parameters code -- a specific code to wait for (int) (optional) bitwise_comparison -- make a bitwise comparison (default=False) """ start = get_time() found = None rt = None if code is None: code = self._default_code self.interface.clear() while True: expyriment._active_exp._execute_wait_callback() read = self.interface.poll() if read is not None: if code is None: #return for every event rt = int((get_time() - start) * 1000) found = read break elif compare_codes(read, code, bitwise_comparison): rt = int((get_time() - start) * 1000) found = read break if Keyboard.process_control_keys(): break if self._logging: expyriment._active_exp._event_file_log( "TriggerInput,received,{0},wait".format(found)) return found, rt def get_triggers(self, code=None, bitwise_comparison=False): """Get list of received triggers. For not missing any triggers the history has to be updated regularly (e.g. by calling this method)! Returns None if no history is used. If bitwise_comparision = True, the function performs a bitwise comparison (logical and) between code and received input and waits until a certain bit pattern is set. Parameters code -- a specific code to get (int) (optional) bitwise_comparison -- make a bitwise comparison (default=False) """ if self.interface.has_input_history: self.interface.clear() counter_list = [] if code is None: code = self._default_code for event in self.interface.input_history.get_whole_buffer(): if code is None: #get them all counter_list.append(event) elif compare_codes(event, code, bitwise_comparison): counter_list.append(event) return counter_list else: return None @property def trigger_count(self, code=None, bitwise_comparison=False): """Get the number of received triggers. For not missing any triggers the history has to be updated regularly (e.g. by calling this method)! Returns None if no history is used. If bitwise_comparision = True, the function performs a bitwise comparison (logical and) between code and received input and waits until a certain bit pattern is set. Parameters code -- a specific code to count (int) (optional) bitwise_comparison -- make a bitwise comparison (default=False) """ if self.interface.has_input_history: self.interface.clear() counter = 0 if code is None: code = self._default_code for event in self.interface.input_history.get_whole_buffer(): if code is None: #count all counter += 1 elif compare_codes(event, code, bitwise_comparison): counter += 1 return counter else: return None python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/defaults.py0000644000175000017500000000476612314561273024012 0ustar oliveroliver""" Default settings for the io package. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from expyriment.misc import constants as _constants # Keyboard keyboard_default_keys = None #Mouse mouse_show_cursor = True mouse_track_button_events = True mouse_track_motion_events = False # StreamingButtonBox streamingbuttonbox_baseline = 0 # OutputFile outputfile_comment_char = "#" outputfile_time_stamp = True outputfile_eol = "\n" # EventFile eventfile_directory = "events" eventfile_delimiter = "," # DataFile datafile_directory = "data" datafile_delimiter = "," # SeriaPort serialport_baudrate = 19200 serialport_bytesize = 8 serialport_parity = 'N' serialport_stopbits = 1 serialport_timeout = 0 serialport_xonxoff = 0 serialport_rtscts = 0 serialport_dsrdtr = 0 serialport_input_history = None serialport_input_timing = 1 serialport_os_buffer_size = 3000 # MarkerOutput markeroutput_default_code = 1 markeroutput_default_duration = None # TriggerInput triggerinput_default_code = 1 # TextInput textinput_position = (0, 0) textinput_ascii_filter = None textinput_length = 25 textinput_message_text_size = None # 'None' is experiment_text_size textinput_message_colour = None # 'None is experiment_text_colour textinput_message_font = None # 'None' will use default system font textinput_message_bold = False textinput_message_italic = False textinput_user_text_size = None # 'None' is experiment_text_size textinput_user_text_colour = None # 'None' is experiment_text_colour textinput_user_text_font = None # 'None' will use default system font textinput_user_text_bold = False textinput_background_colour = None # 'None' is experiment_background_colour textinput_frame_colour = None # 'None' is experiment_text_colour textinput_gap = 6 # Menu textmenu_text_size = 20 textmenu_gap = 2 textmenu_position = (0, 0) textmenu_background_colour = _constants.C_BLACK textmenu_heading_font = None textmenu_text_font = None textmenu_text_colour = _constants.C_GREY textmenu_heading_text_colour = _constants.C_EXPYRIMENT_ORANGE textmenu_select_background_colour = _constants.C_DARKGREY textmenu_select_text_colour = [0, 0, 0] textmenu_select_frame_colour = _constants.C_EXPYRIMENT_ORANGE textmenu_select_frame_line_width = 0 textmenu_justification = 1 textmenu_scroll_menu = 0 python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/__init__.py0000644000175000017500000000163712314561273023734 0ustar oliveroliver"""The io package. This package contains several classes and functions that implement input and output interfaces. See also expyriment.io.extras for more io. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from _screen import Screen from _keyboard import Keyboard from _mouse import Mouse from _files import InputFile, OutputFile, DataFile, EventFile from _parallelport import ParallelPort from _serialport import SerialPort from _gamepad import GamePad from _eventbuttonbox import EventButtonBox from _streamingbuttonbox import StreamingButtonBox from _triggerinput import TriggerInput from _markeroutput import MarkerOutput from _textinput import TextInput from _textmenu import TextMenu from _touchscreenbuttonbox import TouchScreenButtonBox import extras python-expyriment-0.7.0+git34-g55a4e7e/expyriment/io/_streamingbuttonbox.py0000644000175000017500000001262712314561273026273 0ustar oliveroliver""" A streaming button box. This module contains a class implementing a streaming button box. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults import expyriment from expyriment.misc import compare_codes from expyriment.misc._timer import get_time from _keyboard import Keyboard from _input_output import Input, Output class StreamingButtonBox(Input, Output): """A class implementing a streaming button box input.""" def __init__(self, interface, baseline): """Create a streaming button box input. Parameters ---------- interface : io.SerialPort or io.ParallelPort an interface object baseline : int code that is sent when nothing is pressed (int) """ Input.__init__(self) Output.__init__(self) self._interface = interface if baseline is not None: self._baseline = baseline else: self._baseline = defaults.streamingbuttonbox_baseline @property def interface(self): """Getter for interface.""" return self._interface @property def baseline(self): """Getter for baseline.""" return self._baseline @baseline.setter def baseline(self, value): """Setter for baseline""" self._baseline = value def clear(self): """Clear the receive buffer (if available).""" self._interface.clear() if self._logging: expyriment._active_exp._event_file_log("{0},cleared".format( self.__class__.__name__), 2) def check(self, codes=None, bitwise_comparison=False): """Check for response codes. If bitwise_comparison = True, the function performs a bitwise comparison (logical and) between codes and received input. Parameters ---------- codes : int or list, optional certain bit pattern or list of bit pattern to wait for, if codes is not set (None) the function returns for any event that differs from the baseline bitwise_comparison : bool, optional make a bitwise comparison (default=False) Returns ------- key : int key code or None """ while True: read = self._interface.poll() if read is not None: if codes is None and read != self._baseline: if self._logging: expyriment._active_exp._event_file_log( "{0},received,{1},check".format( self.__class__.__name__, read), 2) return read elif compare_codes(read, codes, bitwise_comparison): if self._logging: expyriment._active_exp._event_file_log( "{0},received,{1},check".format( self.__class__.__name__, read)) return read else: return None def wait(self, codes=None, duration=None, no_clear_buffer=False, bitwise_comparison=False, check_for_control_keys=True): """Wait for responses defined as codes. Notes ----- If bitwise_comparision = True, the function performs a bitwise comparison (logical and) between codes and received input and waits until a certain bit pattern is set. This will also by default check for control keys (quit and pause). Thus, keyboard events will be cleared from the cue and cannot be received by a Keyboard().check() anymore! Parameters ---------- codes : int or list, optional bit pattern to wait for if codes is not set (None) the function returns for any event that differs from the baseline duration : int, optional maximal time to wait in ms no_clear_buffer : bool, optional do not clear the buffer (default = False) bitwise_comparison : bool, optional make a bitwise comparison (default = False) check_for_control_keys : bool, optional checks if control key has been pressed (default=True) Returns ------- key : int key code (or None) that quitted waiting rt : int reaction time """ start = get_time() rt = None if not no_clear_buffer: self.clear() while True: expyriment._active_exp._execute_wait_callback() if duration is not None: if int((get_time() - start) * 1000) > duration: return None, None found = self.check(codes, bitwise_comparison) if found is not None: rt = int((get_time() - start) * 1000) break if check_for_control_keys: if Keyboard.process_control_keys(): break if self._logging: expyriment._active_exp._event_file_log( "{0},received,{1},wait".format( self.__class__.__name__, found)) return found, rt python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/0000775000175000017500000000000012314561273022142 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/extras/0000775000175000017500000000000012314561273023450 5ustar oliveroliverpython-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/extras/defaults.py0000644000175000017500000000113412314561273025626 0ustar oliveroliver""" Default settings for misc.extras. This module contains default values for all optional arguments in the init function of all classes in this package. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugin_defaults(__file__), _importer_functions.import_plugin_defaults_from_home(__file__)]: for _defaults in _plugins: exec(_defaults) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/extras/__init__.py0000644000175000017500000000123412314561273025557 0ustar oliveroliver""" The misc extra package. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os import defaults from expyriment import _importer_functions for _plugins in [_importer_functions.import_plugins(__file__), _importer_functions.import_plugins_from_settings_folder(__file__)]: for _plugin in _plugins: try: exec(_plugins[_plugin]) except: print("Warning: Could not import {0}".format( _os.path.dirname(__file__) + _os.sep + _plugin)) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/data_preprocessing.py0000644000175000017500000012772212314561273026401 0ustar oliveroliver"""Data Preprocessing Module. This module contains several classes and functions that help to handle, preprocessing and aggregate Expyriment data files. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os try: import locale as _locale except ImportError: _locale = None # Does not exist on Android import sys as _sys import types as _types from copy import copy as _copy import codecs as _codecs import re as _re try: import numpy as _np except: _np = None from expyriment.misc import unicode2str as _unicode2str from expyriment.misc import str2unicode as _str2unicode def read_datafile(filename, only_header_and_variable_names=False, encoding=None): """Read an Expyriment data file. Returns the data, the variable names, the subject info & the comments: Parameters ---------- filename : str name (fullpath) of the Expyriment data file only_header_and_variable_names : bool, optional if True the function reads only the header and variable names (default=False) Returns ------- data : list of list data array variables : list of str variable names list subject_info : dict dictionary with subject information (incl. date and between subject factors) comments : str string with remaining comments encoding : str, optional the encoding with which the contents of the file will be read """ delimiter = "," variables = None subject_info = {} comments = "" data = [] if encoding is None: with open(filename, 'r') as fl: first_line = fl.readline() encoding = _re.findall("coding[:=]\s*([-\w.]+)", first_line) if encoding == []: second_line = fl.readline() encoding = _re.findall("coding[:=]\s*([-\w.]+)", second_line) if encoding == []: encoding = [None] else: encoding = [encoding] fl = _codecs.open(filename, 'rb', encoding[0], errors='replace') for ln in fl: # parse infos ln = _str2unicode(ln.strip()) if not(ln.startswith("#")): if variables is None: variables = ln.split(delimiter) if only_header_and_variable_names: break else: data.append(ln.split(delimiter)) else: if ln.startswith("#s"): ln = ln.replace("#s", "") tmp = ln.replace("=", ":") tmp = tmp.split(":") if len(tmp) == 2: subject_info[tmp[0].strip()] = tmp[1].strip() else: subject_info["#s{0}".format(len(subject_info))] = ln.strip() elif ln.startswith("#date:"): ln = ln.replace("#date:", "") subject_info["date"] = ln.strip() else: comments = comments + "\n" + ln fl.close() # strip variables for x in range(len(variables)): variables[x] = variables[x].strip() return data, variables, subject_info, comments def write_csv_file(filename, data, varnames=None, delimiter=','): """Write 2D data array to csv file. Parameters ---------- filename : str name (fullpath) of the data file data : list of list 2D array with data (list of list) varnames : list of str, optional array of strings representing variable names delimiter : str, optional delimiter character (default=",") """ _sys.stdout.write("write file: {0}".format(filename)) try: _locale_enc = _locale.getdefaultlocale()[1] except: _locale_enc = "UTF-8" with open(filename, 'w') as f: header = "# -*- coding: {0} -*-\n".format( _locale_enc) f.write(header) if varnames is not None: for c, v in enumerate(varnames): if c > 0: f.write(delimiter) f.write(_unicode2str(v)) f.write("\n") cnt = 0 for row in data: for c, v in enumerate(row): if c > 0: f.write(delimiter) if isinstance(v, unicode): _unicode2str(v) f.write(v) cnt += 1 f.write("\n") print " ({0} cells in {1} rows)".format(cnt, len(data)) def write_concatenated_data(data_folder, file_name, output_file=None, delimiter=','): """Concatenate data and write it to a csv file. All files that start with this name will be considered for the analysis (cf. aggregator.data_files) Notes ----- The function is useful to combine the experimental data and prepare for further processing with other software. It basically wraps Aggregator.write_concatenated_data. Parameters ---------- data_folder : str folder which contains of data of the subjects (str) file_name : str name of the files output_file : str, optional name of data output file. If no specified data will the save to {file_name}.csv delimiter : str, optional delimiter character (default=",") """ return Aggregator(data_folder=data_folder, file_name=file_name)\ .write_concatenated_data(output_file=output_file, delimiter=delimiter) class Aggregator(object): """A class implementing a tool to aggregate Expyriment data. This class is used to handle the multiple data files of a Experiment and process (i.e, aggregate) the data for further analysis Examples -------- This tool helps, for instance, to aggregate your data for certain combinations of independent variables. E.g., data of a numerical magnitude judgement experiment. The code below makes a file with mean and median RTs and a second file with the errors and the number of trials:: from expyriment.misc import data_preprocessing agg = data_preprocessing.Aggregator(data_folder= "./mydata/", file_name = "MagnitudeJudgements") agg.set_computed_variables(["parity = target_number % 2", "size = target_number > 65"]) agg.set_independent_variables(["hand", "size" , "parity"]) agg.set_exclusions(["trial_counter < 0", "error != 0", "RT < 2*std", "RT > 2*std" # remove depending std in iv factor # combination for each subject ]) agg.set_dependent_variables(["mean(RT)", "median(RT)"]) agg.aggregate(output_file="rts.csv") agg.set_exclusions(["trial_counter < 0"]) agg.set_dependent_variables(["sum(error)", "n_trials"]) agg.aggregate(output_file="errors.csv") """ _relations = ["==", "!=", ">", "<", ">=", "<=", "=>", "<="] _operations = ["+", "-", "*", "/", "%"] _dv_functions = ["mean", "median", "sum", "std", "n_trials"] _default_suffix = ".xpd" def __init__(self, data_folder, file_name, suffix=_default_suffix): """Create an aggregator. Parameters ---------- data_folder :str folder which contains of data of the subjects file_name : str name of the files. All files that start with this name will be considered for the analysis (cf. aggregator.data_files) suffix : str, optional if specified only files that end with this particular suffix will be considered (default=.xpd) """ if type(_np) is not _types.ModuleType: message = """Aggregator can not be initialized. The Python package 'numpy' is not installed.""" raise ImportError(message) _version = _np.version.version.split(".") if not _version[0] == 1 and _version[1] < 6: raise ImportError("Expyriment {0} ".format(__version__) + "is not compatible with Numpy {0}.".format( _np.version.version) + "\nPlease install Numpy 1.6 or higher.") print "** Expyriment Data Preprocessor **" self.reset(data_folder, file_name, suffix) def __str__(self): """Getter for the current design as text string.""" design_str = "Data\n" design_str = design_str + u"- file name: " + self._file_name + "\n" design_str = design_str + u"- folder: " + self._data_folder + "\n" design_str = design_str + u"- {0} subject_data sets\n".format( len(self._data_files)) design_str = design_str + u"- {0} variables: {1}\n".format( len(self.variables), self.variables) design_str = design_str + u"- recoded variables: {0}\n".format( self._recode_txt) design_str = design_str + u"- computed variables: {0}\n".format( self._computes_txt) design_str = design_str + u"Design\n" design_str = design_str + u"- independent Variables: {0}\n".format( self._iv_txt) design_str = design_str + u"- dependent Variables: {0}\n".format( self._dv_txt) design_str = design_str + u"- exclude: {0}\n".format( self._exclusions_txt) return design_str def _parse_syntax(self, syntax, throw_exception): """Preprocess relation and operation syntax. Returns relation array. """ rels_ops = _copy(self._relations) rels_ops.extend(self._operations) found = None for ro in rels_ops: if syntax.find(ro) > 0: found = ro break if found is None: if throw_exception: raise RuntimeError("Incorrect syntax: '{0}'".format( _unicode2str(syntax))) else: return None else: syntax = syntax.split(found) var_id = self._get_variable_id(syntax[0].strip(), True) return [var_id, found, syntax[1].strip()] def _get_variable_id(self, variables, throw_exception=False): for cnt, v in enumerate(self.variables): if variables == v: return cnt if (throw_exception): raise RuntimeError("Unknown variable name '{0}'".format( _unicode2str(variables))) return None def _add_independent_variable(self, variable): var_id = self._get_variable_id(variable, True) self._iv.append(var_id) def _add_dependent_variable(self, variable): if variable == "n_trials": self._dv.append([variable, 0]) else: tmp = variable.replace(")", "").split("(") dv_fnc = tmp[0].strip() try: dv_txt = tmp[1].strip() except: raise RuntimeError( "Incorrect syntax for DV: '{0}'".format( _unicode2str(variable))) var_id = self._get_variable_id(dv_txt, True) if dv_fnc in self._dv_functions: self._dv.append([dv_fnc, var_id]) else: raise RuntimeError("Unknown function for dependent variable:" + " '{0}'".format(_unicode2str(dv_fnc))) def _add_compute_variable(self, compute_syntax): """Add a new variable to be computed.""" tmp = compute_syntax.replace("==", "@@") # avoid confusion = & == tmp = tmp.replace("!=", "##") # avoid confusion = & == tmp = tmp.split("=") variable_name = tmp[0].strip() try: syntax = tmp[1].strip() syntax = syntax.replace("@@", "==") syntax = syntax.replace("##", "==") except: raise RuntimeError("Incorrect compute syntax: '{0}'".format( _unicode2str(compute_syntax))) variable_def = self._parse_syntax(syntax, throw_exception=True) if variable_def is None: variable_def = self._parse_operation(syntax, throw_exception=True) if self._get_variable_id(variable_name) is not None: raise RuntimeError("Variable already defined '{0}'".format( _unicode2str(variable_name))) else: self._variables.append(variable_name) self._computes.append([variable_name, variable_def]) def _add_exclusion(self, relation_syntax): """Add an exclusion.""" relation = self._parse_syntax(relation_syntax, throw_exception=True) if relation[1] in self._relations: self._exclusions.append(relation) else: raise RuntimeError("Incorrect exclusion syntax: '{0}'".format( _unicode2str(relation_syntax))) def _add_variable_recoding(self, recode_syntax): """Add a new variable recoding rule.""" error = False tmp = recode_syntax.split(":") if len(tmp) == 2: var_id = self._get_variable_id(tmp[0].strip(), True) excl_array = [] for rule in tmp[1].split(","): rule = rule.split("=") if len(rule) == 2: excl_array.append([rule[0].strip(), rule[1].strip()]) else: error = True else: error = True if error: raise RuntimeError("Incorrect recoding syntax: '{0}'".format( _unicode2str(recode_syntax))) else: self._recode.append([var_id, excl_array]) def _find_idx(self, data, column_id, relation, value): """Find the indices of elements in a data column. Notes ----- It compares of column elements with a value or the elements of a second column, if value is a name of variable. The method deals with numerical and string comparisons and throws an exception for invalid string comparisons. Parameters ---------- data : numpy.array the data column_id : int id of column to compare relation : str relation as string. possible relations: "==", "!=", ">", "<", ">=", "<=", "=>", "<=" value : numeric or string value to find or a variable name """ # is value a variable name second_var_id = self._get_variable_id(value, False) # _add_exclusion try: col = _np.float64(data[:, column_id]) except: # handling strings col = data[:, column_id] try: if second_var_id is not None: val = _np.float64(data[:, second_var_id]) else: val = _np.float64(value) except: # handling strings if second_var_id is not None: val = data[:, second_var_id] else: val = value if value.endswith("std") and (value.find("*") > 0): # remove relative depending std tmp = value.split("*") fac = float(tmp[0]) mean_stds = self._dv_mean_std(data, column_id) idx = [] if relation not in [">", "<", "=>", ">=", "=<", "<="]: raise RuntimeError("Incorrect syntax for " + "exception: '{0} {1}'".format( _unicode2str(relation), _unicode2str(value))) for cnt, row in enumerate(data): #find name of combination combi_str = self.variables[column_id] for iv in self._iv: if isinstance(row[iv], unicode): _row_data = _unicode2str(row[iv]) else: _row_data = row[iv] combi_str = combi_str + "_" + \ "{0}{1}".format(_unicode2str(self.variables[iv]), _row_data) deviation = float(row[column_id]) - mean_stds[combi_str][0] if (relation == ">" and deviation > fac * mean_stds[combi_str][1]) or \ (relation == "=>" or relation == ">=" and deviation >= fac * mean_stds[combi_str][1]) or \ (relation == "<" and deviation < -fac * mean_stds[combi_str][1]) or \ (relation == "=<" or relation == "<=" and deviation <= -fac * mean_stds[combi_str][1]): idx.append(cnt) return idx else: if relation == "!=": comp = (col != val) elif relation == "==": comp = (col == val) elif relation == "<": comp = (col < val) elif relation == ">": comp = (col > val) elif relation == "=<" or relation == "<=": comp = (col <= val) elif relation == "=>" or relation == ">=": comp = (col >= val) else: comp = None # should never occur if isinstance(comp, bool): raise RuntimeError( "Incorrect syntax for " + "exception: '{0} {1}'".format( _unicode2str(relation), _unicode2str(value))) return _np.flatnonzero(comp) def _dv_mean_std(self, data, column_dv_id): """ returns dict with std for iv_combinations """ # get all iv values iv_values = [] for iv in self._iv: tmp = list(set(data[:, iv])) tmp.sort() iv_values.append(tmp) new_variable_names, combinations = self._get_new_variables(iv_values) if len(combinations) == 0: combinations = ["total"] result = {} for cnt, fac_cmb in enumerate(combinations): if fac_cmb == "total": idx = range(0, data.shape[0]) else: # find idx of combinations idx = None for c, iv in enumerate(self._iv): tmp = _np.array(data[:, iv] == fac_cmb[c]) if idx is None: idx = tmp.copy() else: idx = idx & tmp # calc std over idx if len(idx) > 0: result[new_variable_names[cnt+1]] = [ _np.mean(_np.float64(data[idx, column_dv_id])), _np.std(_np.float64(data[idx, column_dv_id]))] # ignore first new var name, which is subject_id return result def _get_new_variables(self, iv_values): """Return the new variables names and factor_combinations. Requires the values for all independent variables iv_values: 2d array. Adds furthermore the defined the subject variables. """ def increase_combination(comb, maxima, pos=None): """Recursive helper function. Returns None if end reached. """ if pos is None: pos = len(comb) - 1 comb[pos] += 1 # increase last position if comb[pos] > maxima[pos]: if pos <= 0: # end reached return None else: for x in range(pos, len(comb)): # set to zero & all pos. behind comb[x] = 0 return increase_combination(comb, maxima, pos - 1) # increase position before else: return comb # calc n levels n_levels = [] for x in iv_values: n_levels.append(len(x) - 1) # build new variables names factor_combinations = [] names = [] if len(iv_values) > 0: tmp_comb = _np.zeros(len(self._iv), dtype=int) while tmp_comb is not None: txt = "" comb_values = [] for c, x in enumerate(tmp_comb): comb_values.append(iv_values[c][x]) if len(txt) > 0: txt = txt + "_" txt = txt + u"{0}{1}".format(self.variables[self._iv[c]], comb_values[-1]) names.append(txt) factor_combinations.append(comb_values) tmp_comb = increase_combination(tmp_comb, n_levels) new_variable_names = ["subject_id"] for sv in self.subject_variables: new_variable_names.append(u"{0}".format(sv)) for dv in self._dv: if dv[0] == "n_trials": dv_txt = "ntr" else: dv_txt = self.variables[dv[1]] if len(names) > 0: for n in names: new_variable_names.append(u"{0}_{1}".format(dv_txt, n)) else: new_variable_names.append(u"{0}_total".format(dv_txt)) return new_variable_names, factor_combinations def reset(self, data_folder, file_name, suffix=_default_suffix): """Reset the aggregator class and clear design. Parameters ---------- data_folder : str folder which contains of data of the subjects file_name : str name of the files. All files that start with this name will be considered for the analysis (cf. aggregator.data_files) suffix : str, optional if specified only files that end with this particular suffix will be considered (default=.xpd) """ self._data_folder = data_folder self._file_name = file_name self._data_files = [] self._variables = [] self._dv = [] self._dv_txt = [] self._iv = [] self._iv_txt = [] self._exclusions = [] self._exclusions_txt = [] self._computes = [] self._computes_txt = [] self._recode_txt = [] self._recode = [] self._subject_variables = [] self._last_data = [] self._added_data = [] self._added_variables = [] self._suffix = suffix for flname in _os.listdir(_os.path.dirname(self._data_folder + "/")): if flname.endswith(self._suffix) and \ flname.startswith(self._file_name): _data, vnames, _subject_info, _comments = \ read_datafile(self._data_folder + "/" + flname) if len(self._variables) < 1: self._variables = vnames else: if vnames != self._variables: message = u"Different variables in ".format(flname) message = message + u"\n{0}".format(vnames) message = message + u"\ninstead of\n{0}".format( self._variables) raise RuntimeError(_unicode2str(message)) self._data_files.append(flname) if len(self._data_files) < 1: raise Exception("No data files found in {0}".format( _unicode2str(self._data_folder))) print "found {0} subject_data sets".format(len(self._data_files)) print "found {0} variables: {1}".format(len(self._variables), [_unicode2str(x) for x in self._variables]) @property def data_folder(self): """Getter for data_folder.""" return self._data_folder @property def data_files(self): """Getter for data_files. The list of the data files considered for the analysis. """ return self._data_files @property def file_name(self): """Getter for file_name.""" return self._file_name @property def variables(self): """Getter for variables. The specified variables including the new computer variables and between subject variables and added variables. """ variables = _copy(self._variables) variables.extend(self._subject_variables) variables.extend(self._added_variables) return variables @property def added_variables(self): """Getter for added variables.""" return self._added_variables @property def computed_variables(self): """Getter for computed variables.""" return self._computes_txt @property def variable_recodings(self): """Getter for variable recodings.""" return self._recode_txt @property def subject_variables(self): """Getter for subject variable.""" return self._subject_variables @property def exclusions(self): """Getter for exclusions.""" return self._exclusions_txt @property def dependent_variables(self): """Getter for dependent variables.""" return self._dv_txt @property def independent_variables(self): """Getter for independent_variables.""" return self._iv_txt def get_data(self, filename, recode_variables=True, compute_new_variables=True, exclude_trials=True): """Read data from from a single Expyriment data file. Notes ----- The function can be only applied on data of aggregator.data_files, that is, on the files in the defined data folder that start with the experiment name. According to the defined design, the result contains recoded data together with the new computed variables, and the subject variables from the headers of the Expyriment data files. Parameters ---------- filename : str name of the Expyriment data file recode_variables : bool, optional set to False if defined variable recodings should not be applied (default=True) compute_new_variables : bool, optional set to False if new defined variables should not be computed (default=True) exclude_trials : bool, optional set to False if exclusion rules should not be applied (default=True) Returns ------- data : numpy.array var_names : list list of variable names info : str subject info comment : str comments in data """ # check filename if filename not in self._data_files: raise RuntimeError("'{0}' is not in the data list\n".format( _unicode2str(filename))) data, _vnames, subject_info, comments = \ read_datafile(self._data_folder + "/" + filename) print " reading {0}".format(_unicode2str(filename)) if recode_variables: for var_id, recoding in self._recode: for old, new in recoding: for row in range(len(data)): if data[row][var_id] == old: data[row][var_id] = new data = _np.array(data, dtype='|S99') # compute new defined variables and append if compute_new_variables: for new_var_name, var_def in self._computes: if var_def[1] in self._relations: # relations are true or false col = _np.zeros([data.shape[0], 1], dtype=int) idx = self._find_idx(data, var_def[0], var_def[1], var_def[2]) col[idx, 0] = 1 else: # operations try: a = _np.float64([data[:, var_def[0]]]).transpose() second_var_id = self._get_variable_id(var_def[2], False) if second_var_id is not None: b = _np.float64( [data[:, second_var_id]]).transpose() else: b = _np.float64(var_def[2]) except: msg = "Error while computing new variable {0}. " + \ "Non-number in variables of {1}" msg.format(new_var_name, filename) raise RuntimeError(msg) if var_def[1] == "+": col = a + b elif var_def[1] == "-": col = a - b elif var_def[1] == "*": col = a * b elif var_def[1] == "/": col = a / b elif var_def[1] == "%": col = a % b data = _np.concatenate((data, col), axis=1) # add subject information for sv in self.subject_variables: try: info = subject_info[sv] except: info = "nan" col = _np.array([[info for _x in range(data.shape[0])]]) data = _np.c_[data, col.transpose()] # _add_exclusion trials if exclude_trials: for exl in self._exclusions: idx = self._find_idx(data, exl[0], exl[1], exl[2]) if len(idx) > 0: data = _np.delete(data, idx, axis=0) var = _copy(self._variables) var.extend(self._subject_variables) return [data, var, subject_info, comments] @property def concatenated_data(self): """Getter for concatenated_data. Notes ----- Returns all data of all subjects as numpy.array and all variables names (including added variables). According to the defined design, the result contains the new computed variables and the subject variables from the headers of the Expyriment data files. If data have been loaded and no new variable or exclusion has been defined the concatenated_data will merely return the previous data without re-processing. Returns ------- data : numpy.array variables : list of str """ if len(self._last_data) > 0: # data are already loaded and unchanged cdata = self._last_data else: cdata = None for flname in self._data_files: tmp = self.get_data(flname)[0] if cdata is None: cdata = tmp else: cdata = _np.concatenate((cdata, tmp), axis=0) self._last_data = cdata # append added data if len(self._added_variables) > 0: if cdata is not None: cdata = _np.concatenate((cdata, self._added_data), axis=1) else: cdata = self._added_data return [cdata, self.variables] def get_variable_data(self, variables): """Returns the column of data as numpy array. Parameters ---------- variables : list of str names of the variables to be extracted Returns ------- data : numpy.array """ if type(variables) != _types.ListType: variables = [variables] cols = [] for v in variables: cols.append(self._get_variable_id(v, throw_exception=True)) data = self.concatenated_data[0] try: data = _np.float64(data[:, cols]) except: data = data[:, cols] return data def add_variables(self, variable_names, data_columns): """Adds a new variable to the data. Notes ----- The amount of variables and added columns must match. The added data must also match the number of rows. Note, manually added variables will be lost if cases will be excluded afterwards via a call of the method `set_exclusions`. Parameters ---------- variable_names : str name of the new variable(s) data_columns : numpy.array the new data columns as numpy array """ d = _np.array(data_columns) data_shape = _np.shape(d) if len(data_shape) < 2: d = _np.transpose([d]) data_shape = (data_shape[0], 1) if type(variable_names) != _types.ListType: variable_names = [variable_names] if len(variable_names) != data_shape[1]: raise RuntimeError( "Amount of variables and added colums doesn't fit.") if data_shape[0] != _np.shape(self.concatenated_data[0])[0]: raise RuntimeError("Number of rows doesn't match.") self._added_variables.extend(variable_names) if len(self._added_data) == 0: self._added_data = d else: self._added_data = _np.concatenate((self._added_data, d), axis=1) self._last_data = [] def write_concatenated_data(self, output_file=None, delimiter=','): """Concatenate data and write it to a csv file. Parameters ---------- output_file : str, optional name of data output file If no specified data will the save to {file_name}.csv delimiter : str delimiter character (default=",") """ if output_file is None: output_file = u"{0}.csv".format(self.file_name) data = self.concatenated_data write_csv_file(filename=output_file, data=data[0], varnames=data[1], delimiter=delimiter) def set_independent_variables(self, variables): """Set the independent variables. Parameters ---------- variables : str or list the name(s) of one or more data variables (aggregator.variables) """ if type(variables) != _types.ListType: self._iv_txt = [variables] else: self._iv_txt = variables self._iv = [] for v in self._iv_txt: self._add_independent_variable(v) self._last_data = [] def set_dependent_variables(self, dv_syntax): """Set dependent variables. Parameters ---------- dv_syntax : str or list syntax describing the dependent variable by a function and variable, e.g. mean(RT) Notes ----- Syntax:: {function}({variable}) {function} -- mean, median, sum, std or n_trials Note: n_trials counts the number of trials and does not require a variable as argument {variable} -- a defined data variable """ if type(dv_syntax) != _types.ListType: self._dv_txt = [dv_syntax] else: self._dv_txt = dv_syntax self._dv = [] for v in self._dv_txt: self._add_dependent_variable(v) self._last_data = [] def set_exclusions(self, rule_syntax): """Set rules to exclude trials from the analysis. The method indicates the rows, which are ignored while reading the data files. It can therefore not be applied on variables that have been added later via `add_variables` and results in a loss of all manually added variables. Setting exclusions requires re-reading of the data files and might be therefore time consuming. Thus, call this method always at the beginning of your analysis script. Parameters ---------- rule_syntax : str or list A string or a list of strings that represent the rules to exclude trials Notes ----- Rule syntax:: {variable} {relation} {variable/value} {variable} -- a defined data variable {relation} -- ==, !=, >, <, >=, <=, => or <= {value} -- string or numeric If value is "{numeric} * std", trails are excluded in which the variable is below or above {numeric} standard deviations from the mean. The relations "==" and "!=" are not allow in this case. The exclusion criterion is apply for each subject and factor combination separately. """ if type(rule_syntax) != _types.ListType: self._exclusions_txt = [rule_syntax] else: self._exclusions_txt = rule_syntax self._exclusions = [] for r in self._exclusions_txt: self._add_exclusion(r) self._last_data = [] self._added_data = [] self._added_variables = [] def set_variable_recoding(self, recoding_syntax): """Set syntax to recode variables. The method defines the variables, which will recoded. It can not be applied on variables that have been added later via `add_variables`. Recoding variables requires re-reading of the data files and might be therefore time consuming. Parameters ---------- rule_syntax : str or list A string or a list of strings that represent the variable recoding syntax Notes ----- Recoding syntax:: {variable}: {old_value1} = {new_value1}, {old_value2} = {new_value2},... """ if type(recoding_syntax) != _types.ListType: self._recode_txt = [recoding_syntax] else: self._recode_txt = recoding_syntax self._recode = [] for syntax in self._recode_txt: self._add_variable_recoding(syntax) self._last_data = [] def set_subject_variables(self, variables): """Set subject variables to be considered for the analysis. The method sets the subject variables. Subject variables are between subject factors or other variables defines in the subject information section (#s) of the Expyriment data file. The method requires a re-reading of the data files and might be therefore time consuming. Parameters ---------- variables : str or list A string or a list of strings that represent the subject variables """ if type(variables) != _types.ListType: self._subject_variables = [variables] else: self._subject_variables = variables self._last_data = [] def set_computed_variables(self, compute_syntax): """Set syntax to compute new variables. The method defines the variables, which will be computed. It can not be applied on variables that have been added manually via `add_variables`. The method requires a re-reading of the data files and might be therefore time consuming. Parameters ---------- compute_syntax : str or list A string or a list of strings that represent the syntax to compute the new variables Notes ----- Compute Syntax:: {new-variable} = {variable} {relation/operation} {variable/value} {new-variable} -- a new not yet defined variable name {variable} -- a defined data variable {relation} -- ==, !=, >, <, >=, <=, => or <= {operation} -- +, -, *, / or % {value} -- string or numeric """ if type(compute_syntax) != _types.ListType: self._computes_txt = [compute_syntax] else: self._computes_txt = compute_syntax self._computes = [] self._variables = read_datafile(self._data_folder + "/" + self._data_files[0], only_header_and_variable_names=True)[1] # original variables for syntax in self._computes_txt: self._add_compute_variable(syntax) self._last_data = [] def print_n_trials(self, variables): """Print the number of trials in the combinations of the independent variables. Notes ----- The functions is for instance useful to quickly check the experimental design. Parameters ---------- variables : str or list A string or a list of strings that represent the names of one or more data variables (aggregator.variables) """ old_iv = self._iv old_dv = self._dv self.set_dependent_variables("n_trials") self.set_independent_variables(variables) result, varnames = self.aggregate() for row in result: print "Subject {0}".format(row[0]) for cnt, var in enumerate(varnames): if cnt > 0: if isinstance(row[cnt], unicode): _row_data = _unicode2str(row[cnt]) else: _row_data = row[cnt] print "\t{0}:\t{1}".format(var[4:], _row_data) print "\n" self._dv = old_dv self._iv = old_iv def aggregate(self, output_file=None, column_subject_id=0): """Aggregate the data as defined by the design. The design will be printed and the resulting data will be return as numpy.array together with the variable names. Parameters ---------- output_file : str, optional name of data output file. If this output_file is defined the function write the results as csv data file column_subject_id : int, optional data column containing the subject id (default=0) Returns ------- result : numpy.array new_variable_names : list of strings """ data, _variables = self.concatenated_data subjects = list(set(data[:, column_subject_id])) subjects.sort() # get all iv values iv_values = [] for iv in self._iv: tmp = list(set(data[:, iv])) tmp.sort() iv_values.append(tmp) new_variable_names, combinations = self._get_new_variables(iv_values) if len(combinations) == 0: combinations = ["total"] # calculate subject wise result = None for sub in subjects: mtx = data[data[:, column_subject_id] == sub, :] row = [sub] # subject info for sv in self.subject_variables: row.append(mtx[0, self._get_variable_id(sv)]) for dv in self._dv: for fac_cmb in combinations: if fac_cmb == "total": idx = range(0, mtx.shape[0]) else: # find idx of combinations idx = None for c, iv in enumerate(self._iv): tmp = _np.array(mtx[:, iv] == fac_cmb[c]) if idx is None: idx = tmp.copy() else: idx = idx & tmp # calc mean over idx if len(idx) > 0: values = mtx[idx, dv[1]] if dv[0] == "median": row.append(_np.median(_np.float64(values))) elif dv[0] == "mean": row.append(_np.mean(_np.float64(values))) elif dv[0] == "sum": row.append(_np.sum(_np.float64(values))) elif dv[0] == "std": row.append(_np.std(_np.float64(values))) elif dv[0] == "n_trials": row.append(values.shape[0]) else: row.append(_np.NaN) else: row.append(_np.NaN) if result is None: result = _np.array([row], dtype='|S99') else: result = _np.r_[result, [row]] if output_file is not None: write_csv_file(output_file, result, new_variable_names) return result, new_variable_names python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/geometry.py0000644000175000017500000001677612314561273024366 0ustar oliveroliver""" The geometry module. This module contains miscellaneous geometry functions for expyriment. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import math as _math import expyriment as _expyriment def coordinates2position(coordinate): """Convert a coordinate on the screen to an expyriment position. Parameters ---------- coordinate : (int, int) coordinate (x,y) to convert Returns ------- coordinate : (int, int) """ screen_size = _expyriment._active_exp.screen.surface.get_size() return (coordinate[0] - screen_size[0] / 2, - coordinate[1] + screen_size[1] / 2) def position2coordinate(position): """Convert an expyriment position to a coordinate on screen. Parameters ---------- coordinate : (int, int) coordinate (x,y) to convert Returns ------- coordinate : (int, int) """ screen_size = _expyriment._active_exp.screen.surface.get_size() return (position[0] + screen_size[0] / 2, - position[1] + screen_size[1] / 2) def position2visual_angle(position, viewing_distance, monitor_size): """Convert an expyriment position (pixel) to a visual angle from center. Parameters ---------- position : (int, int) position (x,y) to convert viewing_distance : numeric viewing distance in cm monitior_size : numeric physical size of the monitor in cm (x, y) Returns ------- angle : (float, float) visual angle for x & y dimension """ screen_size = _expyriment._active_exp.screen.surface.get_size() cm = (position[0] * monitor_size[0] / float(screen_size[0]), position[1] * monitor_size[1] / float(screen_size[1])) angle = (2.0 * _math.atan((cm[0] / 2) / viewing_distance), 2.0 * _math.atan((cm[1] / 2) / viewing_distance)) return (angle[0] * 180 / _math.pi, angle[1] * 180 / _math.pi) def visual_angle2position(visual_angle, viewing_distance, monitor_size): """Convert an position defined as visual angle from center to expyriment position (pixel). Parameters ---------- visual_angle : (numeric, numeric) position in visual angle (x,y) to convert viewing_distance : numeric viewing distance in cm monitior_size : (numeric, numeric) physical size of the monitor in cm (x, y) Returns ------- position : (float, float) position (x,y) """ screen_size = _expyriment._active_exp.screen.surface.get_size() angle = (visual_angle[0] * _math.pi / 360, visual_angle[1] * _math.pi / 360) # angle / 180 / 2 cm = (_math.tan(angle[0]) * viewing_distance * 2, _math.tan(angle[1]) * viewing_distance * 2) return (cm[0] * screen_size[0] / monitor_size[0], cm[1] * screen_size[1] / monitor_size[1]) def points_to_vertices(points): """Returns vertex representation of the points (int, int) in xy-coordinates Parameters ---------- points : (int, int) list of points Returns ------- vtx : list list of vertices """ vtx = [] for i in range(1, len(points)): vtx.append((points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1])) return vtx def lines_intersect(pa, pb, pc, pd): """Return true if two line segments are intersecting Parameters ---------- pa : misc.XYPoint point 1 of line 1 pb : misc.XYPoint point 2 of line 1 pc : misc.XYPoint point 1 of line 2 pb : misc.XYPoint point 2 of line 2 Returns ------- check : bool True if lines intersect """ def ccw(pa, pb, pc): return (pc._y - pa._y) * (pb._x - pa._x) > (pb._y - pa._y) * (pc._x - pa._x) return ccw(pa, pc, pd) != ccw(pb, pc, pd) and ccw(pa, pb, pc) != ccw(pa, pb, pd) class XYPoint: """ The Expyriment point class """ def __init__(self, x=None, y=None, xy=None): """Initialize a XYPoint. Parameters ---------- x : numeric y : numeric xy : (numeric, numeric) xy = (x,y) Notes ----- use `x`, `y` values (two numberic) or the tuple xy=(x,y) """ if x is None: if xy is None: self._x = 0 self._y = 0 else: self._x = xy[0] self._y = xy[1] elif y is None: #if only a tuple is specified: e-g. Point((23,23)) self._x = x[0] self._y = x[1] else: self._x = x self._y = y def __repr__(self): return "(x={0}, y={1})".format(self._x, self._y) @property def x(self): """Getter for x""" return self._x @x.setter def x(self, value): """Getter for x""" self._x = value @property def y(self): """Getter for y""" return self._y @y.setter def y(self, value): """Getter for y""" self._y = value @property def tuple(self): return (self._x, self._y) @tuple.setter def tuple(self, xy_tuple): self._x = xy_tuple[0] self._y = xy_tuple[1] def move(self, v): """Move the point along the coodinates specified by the vector v. Parameters ---------- v : misc.XYPoint movement vector """ self._x = self._x + v._x self._y = self._y + v._y return self def distance(self, p): """Return euclidian distance to the points (p). Parameters ---------- p : misc.XYPoint movement vector Returns ------- dist : float distance to other point p """ dx = self._x - p._x dy = self._y - p._y return _math.sqrt((dx * dx) + (dy * dy)) def rotate(self, degree, rotation_centre=(0, 0)): """Rotate the point counterclockwise in degree around rotation_centre. Parameters ---------- degree : int degree of rotation (default=(0, 0) ) rotation_center : (numeric, numeric) rotation center (x, y) """ p = XYPoint(self._x - rotation_centre[0], self._y - rotation_centre[1]) #cart -> polar ang = _math.atan2(p._x, p._y) r = _math.sqrt((p._x * p._x) + (p._y * p._y)) ang = ang - ((degree / 180.0) * _math.pi); #polar -> cart self._x = r * _math.sin(ang) + rotation_centre[0] self._y = r * _math.cos(ang) + rotation_centre[1] return self def is_inside_polygon(self, point_list): """Return true if point is inside a given polygon. Parameters ---------- point_list : list point list defining the polygon Returns ------- check : bool """ n = len(point_list) inside = False p1 = point_list[0] for i in range(n + 1): p2 = point_list[i % n] if self._y > min(p1._y, p2._y): if self._y <= max(p1._y, p2._y): if self._x <= max(p1._x, p2._x): if p1._y != p2._y: xinters = (self._y - p1._y) * (p2._x - p1._x) / (p2._y - p1._y) + p1._x if p1._x == p2._x or self._x <= xinters: inside = not inside p1 = p2 return inside python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/_buffer.py0000644000175000017500000001164412314561273024130 0ustar oliveroliver""" An event buffer. This module contains a class implementing an event buffer. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import expyriment from _clock import Clock class Buffer(object): """A class implementing a general event buffer.""" def __init__(self, name="Buffer", clock=None): """Create an event buffer. Parameters name -- the name of the buffer (str) clock -- an experimental clock (expyriment.Clock object) (optional) """ self._name = name if clock is not None: self._clock = clock else: if expyriment._active_exp.is_initialized: self._clock = expyriment._active_exp.clock else: self._clock = Clock() self._memory = [] @property def name(self): """Getter for name""" return self._name @property def clock(self): """Getter for clock""" return self._clock @property def memory(self): """Getter for memory [list of tuples (code, rt)]""" return self._memory def add_event(self, event): """Add an event to the buffer. Parameters event -- the event to add (anything) """ item = (event, self._clock.time) self._memory.append(item) def add_events(self, events): """Add a list of events to the buffer. All events get the same time stamp! Parameters events -- the event list to add (list) """ ts = [self._clock.time] * len(events) self._memory.extend(zip(events, ts)) def get_size(self): """Return the number of elements in the buffer.""" return len(self._memory) def get_element(self, position): """Get an element (code, rt) from the buffer. Parameters position -- the position to get the element from (int) """ if position < len(self._memory) and position >= 0: element = self._memory[position] else: element = (0, 0) return element def clear(self): """Clear the buffer.""" self._memory = [] def get_last_event(self): """Get the last event (code, rt) in the buffer.""" if len(self._memory) > 0: element = self._memory[-1] else: element = [0, 0] return element def get_whole_buffer(self): """Get a copy of the buffer.""" bcopy = self._memory[:] return bcopy class ByteBuffer(Buffer): """A class implementing a buffer for bytes. The ByteBuffer class is also used for the input_histoy of serial and parallel ports. """ def __init__(self, name="ByteBuffer", clock=None): """Create a buffer for bytes. Parameters name -- the name of the buffer (str) clock -- an experimental clock (expyriment.Clock object) (optional) """ Buffer.__init__(self, name, clock) def check_set(self, search_byte, search_start_position=0): """ Check if bits are set in buffer bytes and return position. Parameters search_byte -- the byte to search for (int) search_start_position -- position to start search from (int) (optional) """ found_pos = 0 pos = search_start_position for x in range(pos, len(self._memory)): elem = self._memory[x] # OR: at least one of the bits is set if (elem[0] & search_byte) > 0: found_pos = x return found_pos def check_unset(self, search_byte, search_start_position=0): """ Check if bits are NOT set in buffer bytes and return position. Parameters search_byte -- the byte to search for (int) search_start_position -- position to start search from (int) (optional) """ found_pos = 0 pos = search_start_position for x in range(pos, len(self._memory)): elem = self._memory[x] # OR: at least one of the bits is set if (elem[0] ^ 255) & search_byte: found_pos = x return found_pos def check_value(self, value, search_start_position=0): """ Check if value is in buffer bytes and return the position. Parameters value -- the value to check (int) search_start_position -- position to start search from (int) (optional) """ found_pos = 0 pos = search_start_position for x in range(pos, len(self._memory)): elem = self._memory[x] if elem[0] == value: found_pos = x return found_pos python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/_timer.py0000644000175000017500000000725112314561273023776 0ustar oliveroliver"""A high-resolution monotonic timer This module provides a high-resolution timer via the function get_time() Thanks to Luca Filippin for the code examples. Credits and references: http://stackoverflow.com/questions/1205722/how-do-i-get-monotonic-time-durations-in-python http://stackoverflow.com/questions/1824399/get-mach-absolute-time-uptime-in-nanoseconds-in-python https://mail.python.org/pipermail/python-dev/2009-October/093173.html """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' try: import ctypes except: ctypes = None # Does not exist on Android import os from sys import platform _use_time_module = False if platform == 'darwin': # MAC try: class _TimeBase(ctypes.Structure): _fields_ = [ ('numer', ctypes.c_uint), ('denom', ctypes.c_uint) ] _libsys_c = ctypes.CDLL('/usr/lib/system/libsystem_c.dylib') _libsys_kernel = ctypes.CDLL('/usr/lib/system/libsystem_kernel.dylib') _mac_abs_time = _libsys_c.mach_absolute_time _mac_timebase_info = _libsys_kernel.mach_timebase_info _time_base = _TimeBase() if (_mac_timebase_info(ctypes.pointer(_time_base)) != 0): _use_time_module = True def get_time(): """Get high-resolution monotonic time stamp (float) """ _mac_abs_time.restype = ctypes.c_ulonglong return float(_mac_abs_time()) * _time_base.numer / (_time_base.denom * 1e9) get_time() except: _use_time_module = True elif platform.startswith('linux'): # real OS _CLOCK_MONOTONIC = 4 # actually CLOCK_MONOTONIC_RAW see try: class _TimeSpec(ctypes.Structure): _fields_ = [ ('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long) ] _librt = ctypes.CDLL('librt.so.1', use_errno=True) _clock_gettime = _librt.clock_gettime _clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(_TimeSpec)] def get_time(): """Get high-resolution monotonic time stamp (float) """ t = _TimeSpec() if _clock_gettime(_CLOCK_MONOTONIC, ctypes.pointer(t)) != 0: errno_ = ctypes.get_errno() raise OSError(errno_, os.strerror(errno_)) return t.tv_sec + t.tv_nsec * 1e-9 get_time() except: _use_time_module = True elif platform == 'win32': # win32. Code adapted from the psychopy.core.clock source code. try: _fcounter = ctypes.c_int64() _qpfreq = ctypes.c_int64() ctypes.windll.Kernel32.QueryPerformanceFrequency(ctypes.byref(_qpfreq)) _qpfreq = float(_qpfreq.value) _winQPC = ctypes.windll.Kernel32.QueryPerformanceCounter def get_time(): """Get high-resolution monotonic time stamp (float) """ _winQPC(ctypes.byref(_fcounter)) return _fcounter.value/_qpfreq get_time() except: _use_time_module = True else: # Android or something else _use_time_module = True if _use_time_module: import time warn_message = "Failed to initialize monotonic timer. Python's time module will be use." print("Warning: " + warn_message) if platform == 'win32': def get_time(): """Get high-resolution time stamp (float) """ return time.clock() else: def get_time(): """Get high-resolution time stamp (float) """ return time.time() if __name__ == "__main__": print get_time() python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/constants.py0000644000175000017500000001024012314561273024523 0ustar oliveroliver""" The constants module. This module contains expyriment constants. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import os as _os import pygame as _pygame from expyriment.misc import str2unicode as _str2unicode # Keys K_BACKSPACE = _pygame.K_BACKSPACE K_TAB = _pygame.K_TAB K_CLEAR = _pygame.K_CLEAR K_RETURN = _pygame.K_RETURN K_PAUSE = _pygame.K_PAUSE K_ESCAPE = _pygame.K_ESCAPE K_SPACE = _pygame.K_SPACE K_EXCLAIM = _pygame.K_EXCLAIM K_QUOTEDBL = _pygame.K_QUOTEDBL K_HASH = _pygame.K_HASH K_DOLLAR = _pygame.K_DOLLAR K_AMPERSAND = _pygame.K_AMPERSAND K_QUOTE = _pygame.K_QUOTE K_LEFTPAREN = _pygame.K_LEFTPAREN K_RIGHTPAREN = _pygame.K_RIGHTPAREN K_ASTERISK = _pygame.K_ASTERISK K_PLUS = _pygame.K_PLUS K_COMMA = _pygame.K_COMMA K_MINUS = _pygame.K_MINUS K_PERIOD = _pygame.K_PERIOD K_SLASH = _pygame.K_SLASH K_0 = _pygame.K_0 K_1 = _pygame.K_1 K_2 = _pygame.K_2 K_3 = _pygame.K_3 K_4 = _pygame.K_4 K_5 = _pygame.K_5 K_6 = _pygame.K_6 K_7 = _pygame.K_7 K_8 = _pygame.K_8 K_9 = _pygame.K_9 K_COLON = _pygame.K_COLON K_SEMICOLON = _pygame.K_SEMICOLON K_LESS = _pygame.K_LESS K_EQUALS = _pygame.K_EQUALS K_GREATER = _pygame.K_GREATER K_QUESTION = _pygame.K_QUESTION K_AT = _pygame.K_AT K_LEFTBRACKET = _pygame.K_LEFTBRACKET K_BACKSLASH = _pygame.K_BACKSLASH K_RIGHTBRACKET = _pygame.K_RIGHTBRACKET K_CARET = _pygame.K_CARET K_UNDERSCORE = _pygame.K_UNDERSCORE K_BACKQUOTE = _pygame.K_BACKQUOTE K_a = _pygame.K_a K_b = _pygame.K_b K_c = _pygame.K_c K_d = _pygame.K_d K_e = _pygame.K_e K_f = _pygame.K_f K_g = _pygame.K_g K_h = _pygame.K_h K_i = _pygame.K_i K_j = _pygame.K_j K_k = _pygame.K_k K_l = _pygame.K_l K_m = _pygame.K_m K_n = _pygame.K_n K_o = _pygame.K_o K_p = _pygame.K_p K_q = _pygame.K_q K_r = _pygame.K_r K_s = _pygame.K_s K_t = _pygame.K_t K_u = _pygame.K_u K_v = _pygame.K_v K_w = _pygame.K_w K_x = _pygame.K_x K_y = _pygame.K_y K_z = _pygame.K_z K_DELETE = _pygame.K_DELETE K_KP0 = _pygame.K_KP0 K_KP1 = _pygame.K_KP1 K_KP2 = _pygame.K_KP2 K_KP3 = _pygame.K_KP3 K_KP4 = _pygame.K_KP4 K_KP5 = _pygame.K_KP5 K_KP6 = _pygame.K_KP6 K_KP7 = _pygame.K_KP7 K_KP8 = _pygame.K_KP8 K_KP9 = _pygame.K_KP9 K_KP_PERIOD = _pygame.K_KP_PERIOD K_KP_DIVIDE = _pygame.K_KP_DIVIDE K_KP_MULTIPLY = _pygame.K_KP_MULTIPLY K_KP_MINUS = _pygame.K_KP_MINUS K_KP_PLUS = _pygame.K_KP_PLUS K_KP_ENTER = _pygame.K_KP_ENTER K_KP_EQUALS = _pygame.K_KP_EQUALS K_UP = _pygame.K_UP K_DOWN = _pygame.K_DOWN K_RIGHT = _pygame.K_RIGHT K_LEFT = _pygame.K_LEFT K_INSERT = _pygame.K_INSERT K_HOME = _pygame.K_HOME K_END = _pygame.K_END K_PAGEUP = _pygame.K_PAGEUP K_PAGEDOWN = _pygame.K_PAGEDOWN K_F1 = _pygame.K_F1 K_F2 = _pygame.K_F2 K_F3 = _pygame.K_F3 K_F4 = _pygame.K_F4 K_F5 = _pygame.K_F5 K_F6 = _pygame.K_F6 K_F7 = _pygame.K_F7 K_F8 = _pygame.K_F8 K_F9 = _pygame.K_F9 K_F10 = _pygame.K_F10 K_F11 = _pygame.K_F11 K_F12 = _pygame.K_F12 K_F13 = _pygame.K_F13 K_F14 = _pygame.K_F14 K_F15 = _pygame.K_F15 K_NUMLOCK = _pygame.K_NUMLOCK K_CAPSLOCK = _pygame.K_CAPSLOCK K_SCROLLOCK = _pygame.K_SCROLLOCK K_RSHIFT = _pygame.K_RSHIFT K_LSHIFT = _pygame.K_LSHIFT K_RCTRL = _pygame.K_RCTRL K_LCTRL = _pygame.K_LCTRL K_RALT = _pygame.K_RALT K_LALT = _pygame.K_LALT K_RMETA = _pygame.K_RMETA K_LMETA = _pygame.K_LMETA K_LSUPER = _pygame.K_LSUPER K_RSUPER = _pygame.K_RSUPER K_MODE = _pygame.K_MODE K_HELP = _pygame.K_HELP K_PRINT = _pygame.K_PRINT K_SYSREQ = _pygame.K_SYSREQ K_BREAK = _pygame.K_BREAK K_MENU = _pygame.K_MENU K_POWER = _pygame.K_POWER K_EURO = _pygame.K_EURO K_ALL_LETTERS = range(K_a, K_z + 1) K_ALL_DIGITS = range(K_0, K_9 + 1) # Colours C_BLACK = (0, 0, 0) C_WHITE = (255, 255, 255) C_RED = (255, 0, 0) C_GREEN = (0, 255, 0) C_BLUE = (0, 0, 255) C_YELLOW = (255, 255, 0) C_GREY = (200, 200, 200) C_DARKGREY = (150, 150, 150) C_EXPYRIMENT_ORANGE = (255, 150, 50) C_EXPYRIMENT_PURPLE = (160, 70, 250) # Permutation types P_BALANCED_LATIN_SQUARE = 'balanced-latin-square' P_CYCLED_LATIN_SQUARE = 'cycled-latin-square' P_RANDOM = 'random' # Misc _tmp = _os.path.abspath( _os.path.join(_os.path.dirname(__file__), "..", "expyriment_logo.png")) EXPYRIMENT_LOGO_FILE = _str2unicode(_tmp) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/_csv_reader_android.py0000644000175000017500000000151412314561273026467 0ustar oliveroliver"""An emulation of the csv.reader module. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' def reader(the_file): ''' This is a 'dirty' emulation of the csv.reader module only used for Expyriment loading designs under Android. The function reads in a csv file and returns a 2 dimensional array. Parameters ---------- the_file: iterable The file to be parsed. Notes ----- It is strongly suggested the use, if possible, the csv package from the Python standard library. ''' delimiter = "," rtn = [] for row in the_file: rtn.append(map(lambda strn: strn.strip(), row.split(delimiter))) return rtn python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/_clock.py0000644000175000017500000000743112314561273023751 0ustar oliveroliver"""The expyriment clock. This module contains an experimental clock. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import sys import time import types from _timer import get_time import expyriment class Clock(object) : """Basic timing class. Unit of time is milliseconds. """ if sys.platform == 'win32': _cpu_time = time.clock else: _cpu_time = time.time def __init__(self, sync_clock=None): """Create a clock. Parameters ---------- sync_clock : misc.Clock, optional synchronise clock with existing one """ if (sync_clock.__class__.__name__ == "Clock"): self.__init_time = sync_clock.init_time / 1000 else: self.__init_time = get_time() self._init_localtime = time.localtime() self.__start = get_time() @staticmethod def monotonic_time(): """Returns the time of the high-resolution monitonoic timer that is used by Expyriment interally. """ return get_time() @property def init_time(self): """Getter for init time in milliseconds.""" return self.__init_time * 1000 @property def time(self): """Getter for current time in milliseconds since clock init.""" return int((get_time() - self.__init_time) * 1000) @property def cpu_time(self): """Getter for CPU time.""" return self._cpu_time() @property def stopwatch_time(self): """Getter for time in milliseconds since last reset_stopwatch. The use of the stopwatch does not affect the clock time. """ return int((get_time() - self.__start) * 1000) @property def init_localtime(self): """Getter for init time in local time""" return self._init_localtime def reset_stopwatch(self): """"Reset the stopwatch. The use of the stopwatch does not affect the clock time. """ self.__start = get_time() def wait(self, waiting_time, function=None): """Wait for a certain amout of milliseconds. Parameters ---------- waiting_time : int time to wait in milliseconds function : function, optional function to repeatedly execute during waiting loop """ start = self.time if type(function) == types.FunctionType or\ expyriment._active_exp._execute_wait_callback(): while (self.time < start + waiting_time): if type(function) == types.FunctionType: function() expyriment._active_exp._execute_wait_callback() else: looptime = 200 if (waiting_time > looptime): time.sleep((waiting_time - looptime) / 1000) while (self.time < start + waiting_time): pass def wait_seconds(self, time_sec, function=None): """Wait for a certain amout of seconds (see also wait() ). Parameters ---------- time_sec : int time to wait in seconds function : function, optional function to repeatedly execute during waiting loop """ self.wait(time_sec * 1000, function) def wait_minutes(self, time_minutes, function=None): """Wait for a certain amount of minutes (see also wait() ). Parameters ---------- time_minutes : int time to wait in minutes function : function, optional function to repeatedly execute during waiting loop """ self.wait_seconds(time_minutes * 60, function) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/_miscellaneous.py0000644000175000017500000001426012314561273025517 0ustar oliveroliver""" The miscellaneous module. This module contains miscellaneous functions for expyriment. All classes in this module should be called directly via expyriment.misc.*: """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import sys import os try: import locale except ImportError: locale = None # Not available on Android import glob import pygame def compare_codes(input_code, standard_codes, bitwise_comparison=True): """Helper function to compare input_code with a standard codes. Returns a boolean and operates by default bitwise. Parameters ---------- input_code : int code or bitpattern standard_codes : int or list code/bitpattern or list of codes/bitpattern bitwise_comparison : bool, optional (default = True) """ if type(standard_codes) is list: for code in standard_codes: if compare_codes(input_code, code, bitwise_comparison): return True return False else: if input_code == standard_codes: # accounts also for (bitwise) 0==0 & # None==None return True elif bitwise_comparison: return (input_code & standard_codes) else: return False def str2unicode(s, fse=False): """Convert str to unicode. Converts an input str or unicode object to a unicode object without throwing an exception. If fse is False, the first encoding that is tried is the encoding according to the locale settings, falling back to utf-8 encoding if this throws an error. If fse is True, the filesystem encoding is tried, falling back to utf-8. Unicode input objects are return unmodified. Parameters ---------- s : str or unicode input text fse : bool indicates whether the filesystem encoding should be tried first. (default = False) Returns ------- A unicode-type string. """ if isinstance(s, unicode): return s try: locale_enc = locale.getdefaultlocale()[1] except: locale_enc = None if locale_enc is None: locale_enc = u'utf-8' fs_enc = sys.getfilesystemencoding() if fs_enc is None: fs_enc = u'utf-8' if fse: try: u = s.decode(fs_enc) except UnicodeDecodeError: u = s.decode(u'utf-8', u'replace') else: try: u = s.decode(locale_enc) except UnicodeDecodeError: u = s.decode(u'utf-8', u'replace') return u def unicode2str(u, fse=False): """Convert unicode to str. Converts an input str or unicode object to a str object without throwing an exception. If fse is False, the str is encoded according to the locale (with utf-8 as a fallback), otherwise it is encoded with the filesystemencoding. Str input objects are return unmodified. Parameters ---------- u : str or unicode input text fse : bool indicates whether the filesystem encoding should used. (default = False) Returns ------- A str-type string. """ if isinstance(u, str): return u try: locale_enc = locale.getdefaultlocale()[1] except: locale_enc = None if locale_enc is None: locale_enc = u'utf-8' fs_enc = sys.getfilesystemencoding() if fs_enc is None: fs_enc = u'utf-8' if fse: try: s = u.encode(fs_enc) except UnicodeEncodeError: s = u.encode(u'utf-8', u'replace') else: try: s = u.encode(locale_enc) except UnicodeEncodeError: s = u.encode(u'uff-8', u'replace') return s def add_fonts(folder): """Add fonts to Expyriment. All truetype fonts found in the given folder will be added to Expyriment, such that they are found when only giving their name (i.e. without the full path). Parameters ---------- folder : str or unicode the full path to the folder to search for """ pygame.font.init() pygame.sysfont.initsysfonts() for font in glob.glob(os.path.join(folder, "*")): if font[-4:].lower() in ['.ttf', '.ttc']: font = str2unicode(font, fse=True) name = os.path.split(font)[1] bold = name.find('Bold') >= 0 italic = name.find('Italic') >= 0 oblique = name.find('Oblique') >= 0 name = name.replace(".ttf", "") if name.endswith("Regular"): name = name.replace("Regular", "") if name.endswith("Bold"): name = name.replace("Bold", "") if name.endswith("Italic"): name = name.replace("Italic", "") if name.endswith("Oblique"): name = name.replace("Oblique", "") name = ''.join([c.lower() for c in name if c.isalnum()]) pygame.sysfont._addfont(name, bold, italic or oblique, font, pygame.sysfont.Sysfonts) def list_fonts(): """List all fonts installed on the system. Returns a dictionary where the key is the font name and the value is the absolute path to the font file. """ pygame.font.init() d = {} fonts = pygame.font.get_fonts() for font in fonts: d[font] = pygame.font.match_font(font) return d def find_font(font): """Find an installed font given a font name. This will try to match a font installed on the system that is similar to the given font name. Parameters ---------- font : str name of the font Returns ------- font : str the font that is most similar If no font is found, an empty string will be returned. """ pygame.font.init() try: if os.path.isfile(font): pygame.font.Font(unicode2str(font, fse=True), 10) else: pygame.font.Font(unicode2str(font), 10) return font except: font_file = pygame.font.match_font(font) if font_file is not None: return font_file else: return "" python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/defaults.py0000644000175000017500000000054212314561273024322 0ustar oliveroliver""" Default settings for the misc package. This module contains default values for all optional arguments in the init function of all classes in this packge. """ __author__ = 'Florian Krause ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/statistics.py0000644000175000017500000000603612314561273024711 0ustar oliveroliver""" The statistics module. This module contains miscellaneous stastistical functions for expyriment. """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' def sum(data): """Returns the sum of data. The function ignores all non-numerical elements in the data and returns None if no numerical element has been found. In contrast to standard math and numpy functions, this function is robust against type violations. Parameters ---------- data : list list of numerical data Returns ------- out : float or None """ s = 0 elem_found = False for v in data: try: s += v elem_found = True except: pass if elem_found: return s else: return None def mode(data): """Returns the mode, that is, the most frequent value in data. Parameters ---------- data : list list of numerical data Returns ------- out : float or None """ freq = frequence_table(data) Fmax = max(freq.values()) for x, f in freq.items(): if f == Fmax: break return x def mean(data): """Returns the mean of data. Notes ----- The function ignores all non-numerical elements in the data and returns None if no numerical element has been found. In contrast to standard math and numpy functions, this function is robust against type violations. Parameters ---------- data : list list of numerical data Returns ------- out : float or None """ s = 0 cnt = 0 for v in data: try: s += v cnt += 1 except: pass if cnt == 0: return None else: return float(s) / float(cnt) def median(data): """Returns the median of data. Notes ----- The function ignores all non-numerical elements in the data and returns None if no numerical element has been found. In contrast to standard math and numpy functions, this function is robust against type violations. Parameters ---------- data : list list of numerical data Returns ------- out : float or None """ tmp = [] for elem in data: # remove non numerics if isinstance(elem, (int, long, float)): tmp.append(elem) data = sorted(tmp) if len(data) % 2 == 1: return data[(len(data) - 1) / 2 ] else: lower = data[len(data) / 2 - 1] upper = data[len(data) / 2] return (float(lower + upper)) / 2.0 def frequence_table(data): """Returns the frequency table of the data as dictionary. Parameters ---------- data : list list of numerical data Returns ------- out : dict `dict.keys` : values, `dict.values` : frequencies """ freq = {} for x in data: freq[x] = freq.get(x, 0) + 1 return freq python-expyriment-0.7.0+git34-g55a4e7e/expyriment/misc/__init__.py0000644000175000017500000000121012314561273024243 0ustar oliveroliver"""The misc package. This package contains miscellaneous classes, modules and functions. See also expyriment.misc.extras for more misc. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import defaults from _miscellaneous import compare_codes, add_fonts, list_fonts, find_font from _miscellaneous import unicode2str, str2unicode import constants from _buffer import Buffer, ByteBuffer from _clock import Clock import geometry import data_preprocessing import statistics import extras python-expyriment-0.7.0+git34-g55a4e7e/expyriment/__init__.py0000644000175000017500000001415012314561273023317 0ustar oliveroliver"""A Python library for cognitive and neuroscientific experiments. Expyriment is an open-source and platform independent light-weight Python library for designing and conducting timing-critical behavioural and neuroimaging experiments. The major goal is to provide a well-structured Python library for a script-based experiment development with a high priority on the readability of the resulting programme code. It has been tested extensively under Linux and Windows. Expyriment is an all-in-one solution, as it handles the stimulus presentation, recording of I/O events, communication with other devices and the collection and preprocessing of data. It offers furthermore a hierarchical design structure, which allows an intuitive transition from the experimental design to a running programme. It is therefore also suited for students as well as experimental psychologists and neuroscientists with little programming experience. Website: http://www.expyriment.org To cite Expyriment in publications, please refer to the following article: Krause, F. & Lindemann, O. (2013). Expyriment: A Python library for cognitive and neuroscientific experiments. Behavior Research Methods. see http://dx.doi.org/10.3758/s13428-013-0390-6 """ __author__ = 'Florian Krause , \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' import sys as _sys import os as _os from hashlib import sha1 as _sha1 class _Expyriment_object(object): """A class implementing a general Expyriment object. Parent of all stimuli and IO objects """ def __init__(self): """Create an Expyriment object.""" self._logging = True def set_logging(self, onoff): """Set logging of this object on or off Parameters ---------- onoff : bool set logging on (True) or off (False) Notes ----- See also design.experiment.set_log_level fur further information about event logging. """ self._logging = onoff @property def logging(self): """Getter for logging.""" return self._logging def get_version(): """ Return version information about Expyriment and Python. Returns ------- version_info : str Notes ----- For more detailed information see expyriment.get_system_info(). """ pv = "{0}.{1}.{2}".format(_sys.version_info[0], _sys.version_info[1], _sys.version_info[2]) #no use of .major, .minor to ensure MacOS compatibility return "{0} (Revision {1}; Python {2})".format(__version__, \ __revision__, pv) _secure_hash = "" def get_experiment_secure_hash(): """Returns the first six places of the secure hash (sha1) of the main file of the current experiment. Returns ------- hash: string or None first six places of the experiment secure hash (None if no main file can be found) Notes ----- Secure hashes for experiments help to ensure that the correct version is running in the lab. Hash codes are written to all output files and printed in the command line output. If you want to check post hoc the version of your experiment, create the secure hash (sha1) of your expyriment .py-file and compare the first six place with the code in the output file. """ global _secure_hash if _secure_hash != "": return _secure_hash try: with open(_os.path.split(_sys.argv[0])[1]) as f: _secure_hash = _sha1(f.read()).hexdigest()[:6] except: _secure_hash = None return get_experiment_secure_hash() if not(_sys.version_info[0] == 2 and (_sys.version_info[1] == 6 or _sys.version_info[1] == 7)): raise RuntimeError("Expyriment {0} ".format(__version__) + "is not compatible with Python {0}.{1}.".format( _sys.version_info[0], _sys.version_info[1]) + "\nPlease use Python 2.6 or Python 2.7.") else: print("Expyriment {0} ".format(get_version())) if get_experiment_secure_hash() is not None: print("File: {0} ({1})".format(_os.path.split(_sys.argv[0])[1], get_experiment_secure_hash())) try: import pygame as _pygame if _pygame.vernum < (1, 9, 1): raise RuntimeError("Expyriment {0} ".format(__version__) + "is not compatible with Pygame {0}.{1}.{2}.".format( _pygame.vernum) + "\nPlease install Pygame 1.9.") except ImportError: raise ImportError("Expyriment {0} ".format(__version__) + "needs the package 'Pygame')." + "\nPlease install Pygame 1.9.") try: import OpenGL as _OpenGL if int(_OpenGL.version.__version__[0]) < 3: raise RuntimeError("Expyriment {0} ".format(__version__) + "is not compatible with PyOpenGL {0}.{1}.{2}.".format( int(_OpenGL.version.__version__[0]), int(_OpenGL.version.__version__[2]), int(_OpenGL.version.__version__[4]), ) + "\nPlease install PyOpenGL 3.0.") except ImportError: print("No OpenGL support!" + "\nExpyriment {0} ".format(__version__) + "needs the package 'PyOpenGL'." "\nPlease install PyOpenGL 3.0 for OpenGL functionality.") import design _active_exp = design.Experiment("None") import control import stimuli import io import misc misc.add_fonts(misc.str2unicode(_os.path.abspath( _os.path.join(_os.path.dirname(__file__), "_fonts")))) try: import android except ImportError: from _api_reference_tool import show_documentation from _get_system_info import get_system_info import _importer_functions exec(_importer_functions.post_import_hook()) python-expyriment-0.7.0+git34-g55a4e7e/expyriment/_api_reference_tool.py0000644000175000017500000005113112314561273025543 0ustar oliveroliver#!/usr/bin/env python """ The Expyriment documentation This script contains an API reference browser and search GUI interface (TK), as well as a function to call this browser or the online documentation. """ __author__ = 'Florian Krause \ Oliver Lindemann ' __version__ = '0.7.0' __revision__ = '55a4e7e' __date__ = 'Wed Mar 26 14:33:37 2014 +0100' from pydoc import getdoc as _getdoc import inspect as _inspect try: import Tkinter as _tk except: try: import tkinter as _tk # for future (Python 3) except: _tk = None try: import ttk as _ttk # for OS X, if there is no Tile support _root = _ttk.Tk() _root.destroy() except: _ttk = _tk # for Python < 2.7 import expyriment _x = None _y = None def _get_doc_and_function(obj): rtn = [] for var in dir(obj): if not var.startswith("_"): rtn.append(var) return _getdoc(obj), rtn def _read_module(mod, doc_dict): doc_dict[mod.__name__], classes = _get_doc_and_function(mod) for cl in classes: cl = "{0}.{1}".format(mod.__name__, cl) exec("_x =" + cl) doc_dict[cl], functions = _get_doc_and_function(_x) for fnc in functions: fnc = "{0}.{1}".format(cl, fnc) exec("_y =" + fnc) doc_dict[fnc], _tmp = _get_doc_and_function(_y) def _search_doc(search_str, doc_dict): """Search the documentation. Parameters ---------- search_str : string string to search for (str) doc_dict : dict documentation dict to search in(dict """ rtn = [] for k in doc_dict.keys(): if k.lower().find(search_str.lower()) > -1 or\ doc_dict[k].lower().find(search_str.lower()) > -1: rtn.append(k) return rtn def _get_members(item_str): members = [] for member in _inspect.getmembers(eval(item_str)): if not member[0].startswith("_"): members.append(item_str + "." + member[0]) return members def show_GUI(): """Show the GUI.""" import types if type(_tk) is not types.ModuleType: raise ImportError("""API Reference Tool could not be started. The Python package 'Tkinter' is not installed""") # Create the documentation dict doc_dict = {} _read_module(expyriment, doc_dict) _read_module(expyriment.control, doc_dict) _read_module(expyriment.design, doc_dict) _read_module(expyriment.design.extras, doc_dict) _read_module(expyriment.misc, doc_dict) _read_module(expyriment.misc.extras, doc_dict) _read_module(expyriment.misc.data_preprocessing, doc_dict) _read_module(expyriment.io, doc_dict) _read_module(expyriment.io.extras, doc_dict) _read_module(expyriment.stimuli, doc_dict) _read_module(expyriment.stimuli.extras, doc_dict) # Create e root window root = _tk.Tk() root.title("Expyriment ({0}) API Reference Tool".format( expyriment.__version__)) root.minsize(996, 561) # Create the GUI elements left_frame = _ttk.Frame(root) search_frame = _ttk.Frame(left_frame) label = _ttk.Label(search_frame, text="Search:") search_text = _tk.StringVar() entry = _ttk.Entry(search_frame, textvariable=search_text, takefocus=0) entry.delete(0, _tk.END) list_frame_outer = _ttk.Frame(left_frame) list_frame_inner = _ttk.Frame(list_frame_outer) listbox = _tk.Listbox(list_frame_inner, width=40, font=("Courier", 10, "bold")) scroll1 = _ttk.Scrollbar(list_frame_inner, orient=_tk.HORIZONTAL, takefocus=0) scroll1.config(command=listbox.xview) listbox.configure(xscrollcommand=scroll1.set) scroll2 = _ttk.Scrollbar(list_frame_outer, takefocus=0) scroll2.config(command=listbox.yview) listbox.configure(yscrollcommand=scroll2.set) right_frame = _ttk.Frame(root) text_frame_outer = _ttk.Frame(right_frame) text_frame_inner = _ttk.Frame(text_frame_outer) text = _tk.Text(text_frame_inner, width=80, background='white', font=("Courier", 10), wrap=_tk.NONE, state=_tk.DISABLED, takefocus=1) scroll3 = _ttk.Scrollbar(text_frame_inner, orient=_tk.HORIZONTAL, takefocus=0) scroll3.config(command=text.xview) text.configure(xscrollcommand=scroll3.set) scroll4 = _ttk.Scrollbar(text_frame_outer, takefocus=0) scroll4.config(command=text.yview) text.configure(yscrollcommand=scroll4.set) def update_search(_event): """Update the search. This will update the list of found items after each typed letter. Parameters ---------- _event : dummy dummy argument, necessary for callbacks """ global last_sel listbox.delete(0, _tk.END) value = search_text.get() if value == "": items = ["expyriment", ] else: items = _search_doc(value, doc_dict) for index, item in enumerate(items): if type(eval(item)) == types.ModuleType: items[index] = "# " + item elif type(eval(item)) == types.TypeType: items[index] = "+ " + item elif type(eval(item)) == types.MethodType: items[index] = "- " + item elif type(eval(item)) == types.FunctionType: items[index] = "= " + item else: items[index] = "@ " + item items = sorted(items) if items == []: text.config(state=_tk.NORMAL) text.delete(1.0, _tk.END) text.config(state=_tk.DISABLED) last_sel = None for index, item in enumerate(items): listbox.insert(_tk.END, item) if type(eval(item[2:])) == types.ModuleType or \ type(eval(item[2:])) == types.TypeType: listbox.itemconfig(index, fg="blue", selectforeground="blue") listbox.selection_set(_tk.ACTIVE) return True def poll(): """Poll the GUI. This will update the documentation according to the selected item. """ global last_sel text.after(100, poll) sel = listbox.curselection() if sel != (): item = listbox.get(int(sel[0])) if last_sel != item: last_sel = item if item == "..": text.config(state=_tk.NORMAL) text.delete(1.0, _tk.END) text.config(state=_tk.DISABLED) if item != "..": item = item[2:] text.config(state=_tk.NORMAL) text.delete(1.0, _tk.END) text.tag_config("heading", font=("Courier", 12, "bold")) text.insert(_tk.END, item, "heading") text.insert(_tk.END, "\n\n") if type(eval(item)) == types.TypeType: text.insert(_tk.END, doc_dict[item]) definition = "".join(_inspect.getsourcelines( eval(item))[0]) start = definition.find("def __init__(self") + 17 end = definition.find(")", start) if definition[start] == ",": call = "(" + \ definition[start + 1:end].lstrip() + ")" else: call = "()" call = call.replace(" " * 16, " " * len(item.split(".")[-1])) text.insert(_tk.END, "\n\n\n\n") text.tag_config("item", font=("Courier", 10, "bold")) text.tag_config("call", font=("Courier", 10, "italic")) text.insert(_tk.END, item.split(".")[-1], "item") text.insert(_tk.END, call, "call") text.insert(_tk.END, "\n\n") text.insert(_tk.END, _getdoc( eval(item + "." + "__init__"))) elif type(eval(item)) == types.FunctionType: definition = "".join(_inspect.getsourcelines( eval(item))[0]) text.tag_config("item", font=("Courier", 10, "bold")) start = definition.find("(") + 1 end = definition.find(")", start) call = "(" + definition[start:end].lstrip() + ")" call = call.replace( " " + " "*len(item.split(".")[-1]) + " ", " "*len(item.split(".")[-1] + " ")) text.tag_config("call", font=("Courier", 10, "italic")) text.insert(_tk.END, item.split(".")[-1], "item") text.insert(_tk.END, call, "call") text.insert(_tk.END, "\n\n") text.insert(_tk.END, doc_dict[item]) elif type(eval(item)) == types.MethodType: definition = "".join(_inspect.getsourcelines( eval(item))[0]) text.tag_config("item", font=("Courier", 10, "bold")) start = definition.find("(self") + 1 end = definition.find(")", start) if definition[start + 4] == ",": call = "(" + \ definition[start + 5:end].lstrip() + ")" else: call = "()" call = call.replace( " " + " "*len(item.split(".")[-1]) + " ", " "*len(item.split(".")[-1]) + " ") text.tag_config("call", font=("Courier", 10, "italic")) text.insert(_tk.END, item.split(".")[-1], "item") text.insert(_tk.END, call, "call") text.insert(_tk.END, "\n\n") text.insert(_tk.END, doc_dict[item]) elif type(eval(item)) in (types.IntType, types.StringType, types.BooleanType, types.ListType, types.TupleType, types.DictionaryType): pass else: if type(eval(item)) == property: if eval(item).fset is None: text.insert(_tk.END, "Read-only!") else: text.insert(_tk.END, doc_dict[item]) text.config(state=_tk.DISABLED) def member_list(_event, member=None): """Show a list of members of an item. This will show_GUI the list of all members (modules, classes, methods, ...) for a given item. If no item is given, it will take the currently selected one. Parameters ---------- _event : dummy dummy argument, necessary for callbacks member : string, optional item to show_GUI members for """ global last_item if member is not None: item = member else: try: item = listbox.get(int(listbox.curselection()[0])) if item == "..": tmp = item = last_item else: item = item[2:] tmp = item if item == "": update_search(None) else: if type(eval(item)) == types.ModuleType or \ type(eval(item)) == types.TypeType: s = tmp.split(".") if len(s) >= 1: last_item = ".".join(s[0:-1]) else: last_item = None items = _get_members(item) items.insert(0, "..") entry.delete(0, _tk.END) listbox.delete(0, _tk.END) for index, item in enumerate(items): if item != "..": if type(eval(item)) == types.ModuleType: items[index] = "# " + item elif type(eval(item)) == types.TypeType: items[index] = "+ " + item elif type(eval(item)) == types.MethodType: items[index] = "- " + item elif type(eval(item)) == types.FunctionType: items[index] = "= " + item else: items[index] = "@ " + item tmp = items items = [] items.append(tmp[0]) items.extend(sorted(tmp[1:])) for index, item in enumerate(items): listbox.insert(_tk.END, item) if item == ".." or \ type(eval(item[2:])) == types.ModuleType or \ type(eval(item[2:])) == types.TypeType: listbox.itemconfig(index, fg="blue", selectforeground="blue") listbox.selection_set(0) except: pass # Position the GUI elements left_frame.pack(side=_tk.LEFT, expand=1, fill=_tk.BOTH) search_frame.pack(side=_tk.TOP, fill=_tk.BOTH) label.pack(side=_tk.LEFT) entry.pack(side=_tk.LEFT, expand=1, fill=_tk.X) list_frame_outer.pack(side=_tk.BOTTOM, expand=1, fill=_tk.BOTH) list_frame_inner.pack(side=_tk.LEFT, expand=1, fill=_tk.BOTH) listbox.pack(side=_tk.TOP, expand=1, fill=_tk.BOTH) scroll1.pack(side=_tk.BOTTOM, fill=_tk.X) scroll2.pack(side=_tk.LEFT, fill=_tk.Y) right_frame.pack(side=_tk.RIGHT, expand=2, fill=_tk.BOTH) text_frame_outer.pack(side=_tk.BOTTOM, expand=2, fill=_tk.BOTH) text_frame_inner.pack(side=_tk.LEFT, expand=2, fill=_tk.BOTH) text.pack(side=_tk.TOP, expand=2, fill=_tk.BOTH) scroll3.pack(side=_tk.BOTTOM, fill=_tk.X) scroll4.pack(side=_tk.LEFT, fill=_tk.Y) # Create Keybindings root.bind("", lambda x: entry.focus()) root.bind("", lambda x: root.quit()) root.bind("", lambda x: show_help()) listbox.bind("", member_list) listbox.bind("", member_list) entry.bind("", update_search) def show_about(): """Show the about dialogue window""" aboutdialogue = _tk.Toplevel(root, padx=5, pady=5) aboutdialogue.title("About") aboutdialogue.transient(root) aboutdialogue.grab_set() aboutdialogue.focus_set() aboutdialogue.bind("", lambda x: aboutdialogue.destroy()) aboutdialogue.bind("", lambda x: aboutdialogue.destroy()) aboutdialogue.bind("", lambda x: aboutdialogue.destroy()) aboutlabel1 = _ttk.Label(aboutdialogue, text="Expyriment API Reference Tool", font=("Arial", "15", "bold")) aboutlabel1.pack() aboutlabel2 = _ttk.Label(aboutdialogue, text="Expyriment {0}".format( expyriment.get_version()), font=("Arial", "8", "italic")) aboutlabel2.pack() aboutlabel3 = _ttk.Label(aboutdialogue, text="", font=("Arial", "11")) aboutlabel3.pack() aboutlabel4 = _ttk.Label(aboutdialogue, text="Florian Krause ", font=("Arial", "9")) aboutlabel4.pack() aboutlabel5 = _ttk.Label(aboutdialogue, text="Oliver Lindemann ", font=("Arial", "9")) aboutlabel5.pack() def show_help(): """Show the help dialogue window""" helpdialogue = _tk.Toplevel(root, width=200, height=300, padx=5, pady=5) helpdialogue.title("Help Contents") helpdialogue.transient(root) helpdialogue.grab_set() helpdialogue.bind("", lambda x: helpdialogue.destroy()) helpdialogue.bind("", lambda x: helpdialogue.destroy()) helpframe = _ttk.Frame(helpdialogue) helpframe.pack() helptextbox = _tk.Text(helpframe, highlightthickness=0, wrap=_tk.WORD, font=("Courier", 10)) documentation = """The Expyriment API Reference Tool allows you to browse and search the Expyriment API. Double clicking on blue coloured items will show their content. Double clicking on ".." will go up one level. The search is instant, which means every typed letter will update the results immediately. The following symbols are used to denote the type of an item: # Module + Class - Method = Function @ Attribute""" helptextbox.insert('1.0', documentation) helptextbox.pack(side=_tk.LEFT) helptextbox.focus_set() scrollbar = _ttk.Scrollbar(helpframe, takefocus=False, command=helptextbox.yview) scrollbar.pack(side=_tk.RIGHT, fill=_tk.Y) helptextbox.config(state=_tk.DISABLED, yscrollcommand=scrollbar.set) closebutton = _ttk.Button(helpdialogue, text="Close", takefocus=_tk.FALSE, command=helpdialogue.destroy) closebutton.pack() # Create a menu menubar = _tk.Menu(root) filemenu = _tk.Menu(menubar, tearoff=0) filemenu.add_command(label="Quit", command=root.quit, accelerator="Ctrl+Q") menubar.add_cascade(label="File", menu=filemenu) helpmenu = _tk.Menu(menubar, tearoff=0) helpmenu.add_command(label="Contents", command=show_help, accelerator="F1") helpmenu.add_command(label="About", command=show_about) menubar.add_cascade(label="Help", menu=helpmenu) root.config(menu=menubar) # Start the main loop global last_item last_item = None update_search(None) global last_sel last_sel = None poll() entry.focus() _tk.mainloop() def show_documentation(docu_type=None): """Show the Expyriment documentation. Parameters ---------- docu_type : int documentation type. Three options are available: 1) Open online documentation 2) Open online API reference 3) Open API reference and search tool """ from expyriment import get_version def call_info(): print "" print "Call show_documentation with the following arguments to get further information:" print " show_documentation(1) -- Open online documentation" print " show_documentation(2) -- Open online API reference" print " show_documentation(3) -- Open API Reference Tool" print "" import subprocess import os import sys import webbrowser f = os.path.abspath(__file__) path = os.path.abspath(os.path.join(os.path.split(f)[0], "..")) if docu_type is None: print "Welcome to Expyriment {0}".format(get_version()) print "" author = __author__.replace(",", ",\n ") print "Website: http://expyriment.org" print "License: GNU GPL v3" print "Authors: {0}".format(author) call_info() elif docu_type == 1: webbrowser.open( "http://expyriment.org", new=1) elif docu_type == 2: webbrowser.open( "http://docs.expyriment.org/", new=1) elif docu_type == 3: python_executable = sys.executable.replace("pythonw.exe", "python.exe") call = '"' + "{0}".format(python_executable) + \ '" -m expyriment._api_reference_tool' _proc = subprocess.Popen( call, shell=True, stdin=None, stdout=None, cwd=path) else: print "Unknown documentation type" call_info() if __name__ == "__main__": show_GUI() python-expyriment-0.7.0+git34-g55a4e7e/COPYING.txt0000664000175000017500000010451412304063646020661 0ustar oliveroliver GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .