Package VisionEgg :: Package PyroApps :: Module EPhysGUI |
[frames] | no frames]

# Source Code for Module VisionEgg.PyroApps.EPhysGUI

       1  #!/usr/bin/env python
       2  #
       3  # The Vision Egg: EPhysGUI
       4  #
       5  # Copyright (C) 2001-2004 Andrew Straw.
       6  # Copyright (C) 2004 Imran S. Ali, Lachlan Dowd
       7  # Copyright (C) 2004, 2008 California Institute of Technology
       8  #
       9  # Author: Andrew Straw <astraw@users.sourceforge.net>
      10  # URL: <http://www.visionegg.org/>
      11  #
      12  # Distributed under the terms of the GNU Lesser General Public License
      13  # (LGPL). See LICENSE.TXT that came with this file.
      14  #
      15  # $Id: EPhysGUI.py 1455 2008-06-07 15:42:14Z astraw $
      16
      17  import VisionEgg
      18  __version__ = VisionEgg.release_name
      19  __cvs__ = '$Revision: 1455 $'.split()[1]
      20  __date__ = ' '.join('$Date: 2008-06-07 08:42:14 -0700 (Sat, 07 Jun 2008) $'.split()[1:3])
      21  __author__ = 'Andrew Straw <astraw@users.sourceforge.net>'
      22
      23  import sys, socket, re, time, string, types, os
      24  import parser, symbol, token, compiler
      25  import pickle, random, math, threading
      26  import Tkinter, tkMessageBox, tkSimpleDialog, tkFileDialog
      27  import StringIO
      28  import Pyro
      29  import numpy
      30
      31  import VisionEgg
      32  import VisionEgg.PyroClient
      33  import VisionEgg.PyroApps.ScreenPositionGUI
      34  import VisionEgg.GUI
      35  import VisionEgg.ParameterTypes as ve_types
      36
      37  # Add your client modules here
      38  import VisionEgg.PyroApps.TargetGUI
      39  import VisionEgg.PyroApps.MouseTargetGUI
      40  import VisionEgg.PyroApps.FlatGratingGUI
      41  import VisionEgg.PyroApps.SphereGratingGUI
      42  import VisionEgg.PyroApps.SpinningDrumGUI
      43  import VisionEgg.PyroApps.GridGUI
      44  import VisionEgg.PyroApps.ColorCalGUI
      45
      46  import VisionEgg.PyroApps.DropinGUI
      47  import VisionEgg.PyroApps.AST_ext as AST_ext
      48  import VisionEgg.PyroApps.VarTypes as VarTypes
      49
      50  client_list = []
      51  client_list.extend( VisionEgg.PyroApps.TargetGUI.get_control_list() )
      52  client_list.extend( VisionEgg.PyroApps.MouseTargetGUI.get_control_list() )
      53  client_list.extend( VisionEgg.PyroApps.FlatGratingGUI.get_control_list() )
      54  client_list.extend( VisionEgg.PyroApps.SphereGratingGUI.get_control_list() )
      55  client_list.extend( VisionEgg.PyroApps.SpinningDrumGUI.get_control_list() )
      56  client_list.extend( VisionEgg.PyroApps.GridGUI.get_control_list() )
      57  client_list.extend( VisionEgg.PyroApps.ColorCalGUI.get_control_list() )
      58  client_list.extend( VisionEgg.PyroApps.DropinGUI.get_control_list() )
      59
      60 -class ContainedObjectBase:
      61      """Base class to encapsulate objects, provides useful methods when used in GUI"""
      62 -    def __init__(self):
      63          raise RuntimeError("Abstract base class!")
      64 -    def get_str_30(self):
      65          return "**** this is a generic str_30 ****"
      66 -    def get_contained(self):
      67          return self.contained
      68      header = "unknown parameters"
      69
      70 -class ScrollListFrame(Tkinter.Frame):
      71 -    def __init__(self,master=None,list_of_contained_objects=None,contained_objectbject_maker=None,
      72                   container_class=ContainedObjectBase,
      73                   **cnf):
      74          Tkinter.Frame.__init__(self, master, **cnf)
      75          if list_of_contained_objects is None:
      76              self.list = []
      77          else:
      78              self.list = list_of_contained_objects
      79          self.container_class = container_class
      80
      81          # allow column to expand
      82          self.columnconfigure(0,weight=1)
      83
      84          # The frame that has the list and the vscroll
      85          self.frame = Tkinter.Frame(self,borderwidth=2)
      86          self.frame.grid(row=0,sticky="nwes")
      87
      88          # allow column to expand
      89          self.frame.columnconfigure(0,weight=1)
      90
      91          self.frame.vscroll = Tkinter.Scrollbar(self.frame,orient=Tkinter.VERTICAL)
      92          self.frame.hscroll = Tkinter.Scrollbar(self.frame,orient=Tkinter.HORIZONTAL)
      93          self.frame.title = Tkinter.Listbox(
      94              self.frame,
      95              relief=Tkinter.FLAT,
      96              font=('courier',10,'bold'),
      97              height=1,
      98  #            selectbackground='#eed5b7',
      99  #            selectborderwidth=0,
     100  #            selectmode=None,
     101              exportselection=0)
     102          self.frame.title.insert(Tkinter.END, self.container_class.header)
     103          self.frame.list = Tkinter.Listbox(
     104              self.frame,
     105              relief=Tkinter.SUNKEN,
     106              font=('courier',10,'normal'),
     107              width=40, height=10,
     108              selectbackground='#eed5b7',
     109              selectborderwidth=0,
     110              selectmode=Tkinter.BROWSE,
     111              xscroll=self.frame.hscroll.set,
     112              yscroll=self.frame.vscroll.set,
     113              exportselection=0)
     114
     115          self.frame.hscroll['command'] = self.delegate_hscroll
     116          self.frame.hscroll.grid(row=3,column=0,sticky='we')
     117          self.frame.vscroll['command'] = self.frame.list.yview
     118          self.frame.vscroll.grid(row=2,column=1,sticky='ns')
     119          self.frame.title.grid(row=1,column=0,ipady=0,pady=0,sticky='we')
     120          self.frame.list.grid(row=2,column=0,sticky='nwes')
     121          self.frame.list.bind('<Double-Button-1>',self.edit_selected)
     122
     123          # The buttons on bottom
     124          self.bar = Tkinter.Frame(self,borderwidth=2)
     125          self.bar.grid(row=1,sticky="we")
     126          self.bar.add = Tkinter.Button(self.bar,text='Add...',command=self.add_new)
     127          self.bar.add.grid(row=0,column=0,sticky='we')
     128          self.bar.edit = Tkinter.Button(self.bar,text='Edit...',command=self.edit_selected)
     129          self.bar.edit.grid(row=0,column=1,sticky='we')
     130          self.bar.remove = Tkinter.Button(self.bar,text='Remove',command=self.remove_selected)
     131          self.bar.remove.grid(row=0,column=2,sticky='we')
     132          self.bar.move_up = Tkinter.Button(self.bar,text='Up',command=self.move_selected_up)
     133          self.bar.move_up.grid(row=0,column=3,sticky='we')
     134          self.bar.move_down = Tkinter.Button(self.bar,text='Down',command=self.move_selected_down)
     135          self.bar.move_down.grid(row=0,column=4,sticky='we')
     136          self.bar.tk_menuBar(self.bar.add,self.bar.remove)
     137          #Lachie- My bar for setting parent
     138          self.bar.merge = Tkinter.Button(self.bar,text='Merge/Unmerge',command=self.make_merge)
     139          self.bar.merge.grid(row=0,column=5,sticky='we')
     140          self.bar.tk_menuBar(self.bar.add,self.bar.remove)
     141          self.update_now()
     142
     143 -    def list2D_coordinates(self, main_index, main_list):
     144          # This is a function for finding the 2-d
     145          # list coordinates of an element which may be inside a
     146          # list-nested-list.
     147          # eg. if x = [[e, e, e], [e], [e, e]]
     148          # Then the coordinates of the element at index 4 is: (2, 0)
     149          #
     150          # Initialization:
     151          i = -1
     152          j = -1
     153          element_count = 0
     154          # Main body:
     155          for nested_list in main_list:
     156              j = -1
     157              i = i + 1
     158              for element in nested_list:
     159                  j = j + 1
     160                  element_count = element_count + 1
     161                  if (element_count - 1) == main_index:
     162                      return [i, j]
     163          # Unsuccessful exit:
     164          return [-1, -1]
     165
     166 -    def delegate_hscroll(self,*args,**kw):
     167          self.frame.title.xview(*args,**kw)
     168          self.frame.list.xview(*args,**kw)
     169
     170 -    def get_list_uncontained(self):
     171          results = []
     172          for contained_object_item in self.list:
     173              #results.append( contained_object_item.get_contained() )
     174              results.append( contained_object_item )
     175          return results
     176
     177 -    def update_now(self):
     178          self.frame.list.delete(0,Tkinter.END)
     179          max_len = 0
     180          for loop_container in self.list:
     181              for loop in loop_container:
     182                  item_str_30 = loop.get_str_30()
     183                  max_len = max(max_len,len(item_str_30))
     184                  self.frame.list.insert(Tkinter.END,item_str_30)
     185              self.frame.list.insert(Tkinter.END,"")
     186          self.frame.title.delete(0,Tkinter.END)
     187          self.frame.title.insert(Tkinter.END, self.container_class.header.ljust(max_len))
     188
     189 -    def add_new(self):
     190          contained_object = self.make_contained_object(self.container_class)
     191          if contained_object:
     192              self.list.append( [contained_object] )
     193          self.update_now()
     194
     195 -    def edit_selected(self,dummy_arg=None):
     196          selected = self.get_selected()
     197          # Get 2-D list coordinates of selected object of class "LoopContainedObject":
     198          loop_coordinates = self.list2D_coordinates(selected, self.list)
     199          main_list_index = loop_coordinates[0]
     200          loop_list_index = loop_coordinates[1]
     201          if selected is not None:
     202              if len(self.list[main_list_index]) == 1:
     203                  orig_contained_object = self.list[main_list_index][loop_list_index]
     204                  modified_contained_object = self.edit_contained_object( orig_contained_object )
     205                  if modified_contained_object is not None: # "Cancel" press results in None
     206                      self.list[main_list_index][loop_list_index] = modified_contained_object
     207                  self.update_now()
     208              else:
     209                  tkMessageBox.showerror("Cannot edit this variable", "This variable needs to be isolated/unmerged")
     210
     211 -    def remove_selected(self):
     212          selected = self.get_selected()
     213          # Get 2-D list coordinates of selected object of class "LoopContainedObject":
     214          loop_coordinates = self.list2D_coordinates(selected, self.list)
     215          main_list_index = loop_coordinates[0]
     216          loop_list_index = loop_coordinates[1]
     217          if selected is not None:
     218              del self.list[main_list_index][loop_list_index]
     219              if self.list[main_list_index] == []:
     220                  del self.list[main_list_index]
     221              self.update_now()
     222
     223 -    def move_selected_up(self,dummy_arg=None):
     224          selected = self.get_selected()
     225          # Get 2-D list coordinates of selected object of class "LoopContainedObject":
     226          loop_coordinates = self.list2D_coordinates(selected, self.list)
     227          main_list_index = loop_coordinates[0]
     228          loop_list_index = loop_coordinates[1]
     229          new_selected_index = selected
     230          if selected is not None:
     231              # If the selected variable is first in its "loop_list":
     232              if loop_list_index == 0:
     233                  # If not the first "loop_list":
     234                  if main_list_index != 0:
     235                      # Then we move up the entire "loop_list":
     236                      selected_loop_list = self.list[main_list_index]
     237                      del self.list[main_list_index]
     238                      new_main_list_index = main_list_index - 1
     239                      self.list.insert(new_main_list_index, selected_loop_list)
     240                      new_selected_index = selected - len(self.list[main_list_index])
     241                      self.update_now()
     242
     243              # Else we just move up a variable within a "loop_list":
     244              else:
     245                  selected_loop_container = self.list[main_list_index][loop_list_index]
     246                  del self.list[main_list_index][loop_list_index]
     247                  new_loop_list_index = loop_list_index - 1
     248                  self.list[main_list_index].insert(new_loop_list_index, selected_loop_container)
     249                  new_selected_index = selected - 1
     250                  self.update_now()
     251
     252              new_selected_index = self.map_to_listbox_index(new_selected_index)
     253              self.frame.list.selection_set(new_selected_index)
     254
     255 -    def move_selected_down(self,dummy_arg=None):
     256          selected = self.get_selected()
     257          # Get 2-D list coordinates of selected object of class "LoopContainedObject":
     258          loop_coordinates = self.list2D_coordinates(selected, self.list)
     259          main_list_index = loop_coordinates[0]
     260          loop_list_index = loop_coordinates[1]
     261          new_selected_index = selected
     262          if selected is not None:
     263              # If the selected variable is last in its "loop_list":
     264              if loop_list_index == (len(self.list[main_list_index]) - 1):
     265                  # If not the last "loop_list":
     266                  if main_list_index != (len(self.list) - 1):
     267                      # Then we move down the entire "loop_list":
     268                      selected_loop_list = self.list[main_list_index]
     269                      del self.list[main_list_index]
     270                      new_main_list_index = main_list_index + 1
     271                      self.list.insert(new_main_list_index, selected_loop_list)
     272                      new_selected_index = selected + len(self.list[main_list_index])
     273                      self.update_now()
     274
     275              # Else we just move down a variable within a "loop_list":
     276              #elif loop_list_index != (len(self.list[main_list_index]) - 1):
     277              else:
     278                  selected_loop_container = self.list[main_list_index][loop_list_index]
     279                  del self.list[main_list_index][loop_list_index]
     280                  new_loop_list_index = loop_list_index + 1
     281                  self.list[main_list_index].insert(new_loop_list_index, selected_loop_container)
     282                  new_selected_index = selected + 1
     283                  self.update_now()
     284              #else:
     285                  #tkMessageBox.showerror("Cannot move this variable down", "Select unmerge instead")
     286
     287              new_selected_index = self.map_to_listbox_index(new_selected_index)
     288              self.frame.list.selection_set(new_selected_index)
     289
     290
     291
     292
     293
     294
     295
     296 -    def make_merge(self):
     297          # Notes:
     298          # "self.list" is a list of lists, each of which, a
     299          # "loop_list", contains "LoopContainedObject" class objects:
     300          # eg. [[a], [b, c], [d]]
     301          selected = self.get_selected()
     302
     303          merge_error = 0
     304          merge_error_msg = ""
     305
     306          # The purpose of this function is to "merge" selected objects of class
     307          # "LoopContainedObject" into a preceding list:
     308          # eg. [[a], [b, c], [d]] => [[a], [b, c, d]]]
     309          # where selected 'd' was "merged" into preceding list.
     310          # Note, this function can also perform the reverse, provided that the
     311          # the selected object of class "LoopContainedObject" is the LAST one
     312          # in its "loop_list".
     313          # Supported cases:
     314          # [[a], [b*, c], [d]] => [[a, b, c], [d]] merge
     315          # [[a], [b, c*], [d]] => [[a], [b], [c, d]] unmerge
     316          # Unsupported cases:
     317          # [[a], [b, c*, d], [e]] => cannot unmerge!
     318
     319          # Get 2-D list coordinates of selected object of class "LoopContainedObject":
     320          loop_coordinates = self.list2D_coordinates(selected, self.list)
     321          main_list_index = loop_coordinates[0]
     322          loop_list_index = loop_coordinates[1]
     323
     324          # Checking that an item is actually selected:
     325          if selected is not None:
     326
     327              selected_loop_list = self.list[main_list_index]
     328              selected_loop_container = selected_loop_list[loop_list_index]
     329              preceding_loop_container = self.list[main_list_index - 1][0]
     330
     331              # Trying to perform merge?
     332              if loop_list_index == 0:
     333
     334                  # Ensure selected "LoopContainerObject" is not in first "loop_list":
     335                  if main_list_index > 0:
     336
     337                      # Can only carry out merge if "Loop" object sequence lengths are
     338                      # the same length within a "loop_list":
     339                      if len(selected_loop_container.contained.parameters.sequence) == len(preceding_loop_container.contained.parameters.sequence):
     340
     341                          # Perform the merge. All variables that are currently merged with the selected variable,
     342                          # are merged with the new variable(s) as well.
     343                          i = 0
     344                          max_index = len(selected_loop_list)
     345                          while i < max_index:
     346                              dummy_loop_container = selected_loop_list[0]
     347                              del self.list[main_list_index][0]
     348                              self.list[main_list_index - 1].append(dummy_loop_container)
     349                              i = i + 1
     350
     351                          # Remove the selected "loop_list" if it is now empty:
     352                          if self.list[main_list_index] == []:
     353                              del self.list[main_list_index]
     354
     355                      else:
     356                          merge_error = 1
     357                          merge_error_msg = "Cannot merge variables with different sequence lengths"
     358
     359                  else:
     360                      merge_error = 3
     361                      #merge_error_msg = "Variable is at the top level"
     362
     363              # Trying to perform an "unmerge":
     364              else:
     365
     366                  # Ensure selected "LoopContainerObject" is last object in its "loop_list":
     367                  if loop_list_index == (len(selected_loop_list) - 1):
     368
     369                      # Perform the unmerge:
     370                      del self.list[main_list_index][loop_list_index]
     371                      self.list.insert((main_list_index + 1), [selected_loop_container])
     372
     373                  else:
     374                      merge_error = 2
     375                      merge_error_msg = "Unmerge lowest variable in this cluster first"
     376
     377          if merge_error == 1:
     378              tkMessageBox.showerror("Cannot perform merge", merge_error_msg)
     379          elif merge_error == 2:
     380              tkMessageBox.showerror("Cannot perform unmerge", merge_error_msg)
     381          elif merge_error == 3:
     382              # non critical errors
     383              pass
     384          else:
     385              #debugger:
     386              #print len(self.list)
     387              #print ""
     388              #for x in self.list:
     389              #    print len(x)
     390              #print "--------------"
     391              self.update_now()
     392
     393
     394
     395 -    def make_contained_object(self, container_class):
     396          """Factory function for ContainedObjectBase"""
     397          if container_class == LoopContainedObject:
     398              return self.make_loop_contained_object()
     399          params = {}
     400          p = container_class.contained_class.parameters_and_defaults
     401          keys = p.keys()
     402          keys.sort()
     403          for pname in keys:
     404              if p[pname][1] == ve_types.String:
     405                  params[pname] = tkSimpleDialog.askstring(pname,pname,initialvalue=p[pname][0])
     406              elif p[pname][1] == ve_types.Integer:
     407                  params[pname] = tkSimpleDialog.askinteger(pname,pname,initialvalue=p[pname][0])
     408              elif p[pname][1] == ve_types.Real:
     409                  params[pname] = tkSimpleDialog.askfloat(pname,pname,initialvalue=p[pname][0])
     410              elif p[pname][1] == ve_types.Sequence:
     411                  params[pname] = eval("["+tkSimpleDialog.askstring(pname,pname,initialvalue="1,2,3")+"]")
     412                  if type(params[pname]) is not types.ListType:
     413                      raise ValueError("You must enter a list in the form of '[1,2,3]'")
     414              else:
     415                  raise NotImplementedError("Don't know about type %s"%(p[pname][1],))
     416              if params[pname] is None:
     417                  raise RuntimeError("Input cancelled")
     418          contained = container_class.contained_class(**params) # call constructor
     419          return container_class(contained)
     420
     421 -    def edit_contained_object(self, contained_object):
     422          if not isinstance(contained_object,LoopContainedObject):
     423              raise NotImplementedError("")
     424          orig_contained = contained_object.get_contained()
     425          d = LoopParamDialog(self, title="Loop Parameters", orig_values=orig_contained )
     426          if d.result:
     427              return LoopContainedObject(d.result)
     428          else:
     429              return
     430
     431 -    def make_loop_contained_object(self):
     432          d = LoopParamDialog(self, title="Loop Parameters" )
     433          if d.result:
     434              return LoopContainedObject(d.result)
     435          else:
     436              return
     437
     438      # Returns index of selected item ignoring blank listbox entries:
     439      # eg. if listbox had:
     440      #
     441      # 0 a
     442      # 1 b
     443      # 2
     444      # 3 c
     445      # 4 d
     446      #
     447      # Then the index of element 'c' would be 2
     448 -    def get_selected(self):
     449          items = self.frame.list.curselection()
     450          try:
     451              items = map(int, items)
     452
     453          except ValueError: pass
     454          if len(items) > 0:
     455              selected_item_index = items[0]
     456              if self.frame.list.get(selected_item_index) != "":
     457                  blankentrycount = 0
     458                  i = 0
     459                  while i < selected_item_index:
     460                      if self.frame.list.get(i) == "":
     461                          blankentrycount = blankentrycount + 1
     462                      i = i + 1
     463                  return (selected_item_index - blankentrycount)
     464
     465          return None
     466
     467
     468      # Performs reverse of above:
     469      # eg. if listbox had:
     470      #
     471      # 0 a
     472      # 1 b
     473      # 2
     474      # 3 c
     475      # 4 d
     476      #
     477      # Then "mapping" of given index 2 would result in return value of 3
     478 -    def map_to_listbox_index(self, index):
     479          validentrycount = 0
     480          i = 0
     481          while i < self.frame.list.size():
     482              if self.frame.list.get(i) != "":
     483                  validentrycount = validentrycount + 1
     484              if validentrycount == (index + 1):
     485                  return i
     486              i = i + 1
     487          return -1
     488  ###################################################
     489
     490 -class Loop(VisionEgg.ClassWithParameters):
     491      parameters_and_defaults = {'variable':('<repeat>',
     492                                             ve_types.String),
     493                                 'sequence':([1, 1, 1],
     494                                             ve_types.Sequence(ve_types.Real)),
     495                                 'rest_duration_sec':(1.0,
     496                                                      ve_types.Real)}
     497      __slots__ = (
     498          'num_done',
     499          )
     500
     501
     502 -    def __init__(self,**kw):
     503          VisionEgg.ClassWithParameters.__init__(self,**kw)
     504          self.num_done = 0
     505 -    def is_done(self):
     506          return self.num_done >= len(self.parameters.sequence)
     507 -    def get_current(self):
     508          return self.parameters.sequence[self.num_done]
     509 -    def advance(self):
     510          self.num_done += 1
     511 -    def reset(self):
     512          self.num_done = 0
     513
     514 -class LoopContainedObject(ContainedObjectBase):
     515      """Container for Loop class"""
     516      contained_class = Loop
     517      header = "     variable    rest   N  values"
     518 -    def __init__(self,contained):
     519          self.contained = contained
     520 -    def get_str_30(self):
     521          p = self.contained.parameters
     522          seq_str = ""
     523          for val in p.sequence:
     524              seq_str += str(val) + " "
     525          name_str = p.variable
     526          if len(name_str) > 15:
     527              name_str = name_str[:15]
     528          return "% 15s % 4s % 4d  % 4s"%(name_str, str(p.rest_duration_sec), len(p.sequence), seq_str)
     529
     530 -class LoopParamDialog(tkSimpleDialog.Dialog):
     531 -    def __init__(self,*args,**kw):
     532          #intercept orig_values argument
     533          if kw.has_key('orig_values'):
     534              self.orig_values = kw['orig_values']
     535              del kw['orig_values']
     536          else:
     537              self.orig_values = None
     538          return tkSimpleDialog.Dialog.__init__(self, *args, **kw )
     539
     540 -    def body(self,master):
     541          Tkinter.Label(master,
     542                        text="Add sequence of automatic variable values",
     543                        font=("Helvetica",12,"bold"),).grid(row=0,column=0,columnspan=2)
     544
     545          var_frame = Tkinter.Frame(master,
     546                                    relief=Tkinter.GROOVE,
     547                                    borderwidth=2)
     548          var_frame.grid(row=1,column=0)
     549
     550          sequence_frame = Tkinter.Frame(master)
     551          sequence_frame.grid(row=1,column=1)
     552
     553          rest_dur_frame = Tkinter.Frame(master)
     554          rest_dur_frame.grid(row=2,column=0,columnspan=2)
     555
     556          # loopable variable frame stuff
     557          global loopable_variables
     558          num_cols = int(math.ceil(len(loopable_variables)/10.0)) # 10 variables per column
     559
     560          var_frame_row = 0
     561          Tkinter.Label(var_frame,
     562                        text="Select a variable",
     563                        font=("Helvetica",12,"bold"),).grid(row=var_frame_row,
     564                                                            column=0,
     565                                                            columnspan=num_cols)
     566
     567          self.var_name = Tkinter.StringVar()
     568          self.var_name.set("<repeat>")
     569          var_names = loopable_variables[:] # copy
     570          var_names.sort()
     571
     572          var_frame_row += 1
     573          Tkinter.Radiobutton( var_frame,
     574                       text="Repeat (Average)",
     575                       variable=self.var_name,
     576                       value="<repeat>",
     577                       anchor=Tkinter.W).grid(row=var_frame_row,
     578                                              column=0,
     579                                              sticky="w")
     580          var_frame_row += 1
     581          for var_name in var_names:
     582              use_row = var_frame_row%10+1
     583              Tkinter.Radiobutton( var_frame,
     584                                   text=var_name,
     585                                   variable=self.var_name,
     586                                   value=var_name,
     587                                   anchor=Tkinter.W).grid(row=use_row,
     588                                                          column=int(math.floor(var_frame_row/10.)),
     589                                                          sticky="w")
     590              var_frame_row += 1
     591
     592          # sequence entry frame
     593          seq_row = 0
     594          Tkinter.Label(sequence_frame,
     595                        text="Sequence values",
     596                        font=("Helvetica",12,"bold"),).grid(row=seq_row,column=0,columnspan=2)
     597
     598          seq_row += 1
     599          self.sequence_type = Tkinter.StringVar()
     600          self.sequence_type.set("manual")
     601
     602          Tkinter.Radiobutton( sequence_frame,
     603                       text="Manual:",
     604                       variable=self.sequence_type,
     605                       value="manual",
     606                       anchor=Tkinter.W).grid(row=seq_row,column=0,sticky="w")
     607
     608          self.sequence_manual_string = Tkinter.StringVar()
     609          self.sequence_manual_string.set("[1,2,3]")
     610          Tkinter.Entry(sequence_frame,
     611                        textvariable=self.sequence_manual_string).grid(row=seq_row,column=1)
     612
     613          seq_row += 1
     614          Tkinter.Radiobutton( sequence_frame,
     615                       text="Linear:",
     616                       variable=self.sequence_type,
     617                       value="linear",
     618                       anchor=Tkinter.W).grid(row=seq_row,column=0,sticky="w")
     619
     620          self.lin_start_tk = Tkinter.DoubleVar()
     621          self.lin_start_tk.set(1.0)
     622          self.lin_stop_tk = Tkinter.DoubleVar()
     623          self.lin_stop_tk.set(100.0)
     624          self.lin_n_tk = Tkinter.IntVar()
     625          self.lin_n_tk.set(3)
     626
     627          lin_frame = Tkinter.Frame( sequence_frame)
     628          lin_frame.grid(row=seq_row,column=1)
     629          Tkinter.Label(lin_frame,text="start:").grid(row=0,column=0)
     630          Tkinter.Entry(lin_frame,textvariable=self.lin_start_tk,width=6).grid(row=0,column=1)
     631          Tkinter.Label(lin_frame,text="  stop:").grid(row=0,column=2)
     632          Tkinter.Entry(lin_frame,textvariable=self.lin_stop_tk,width=6).grid(row=0,column=3)
     633          Tkinter.Label(lin_frame,text="  N:").grid(row=0,column=4)
     634          Tkinter.Entry(lin_frame,textvariable=self.lin_n_tk,width=6).grid(row=0,column=5)
     635
     636          seq_row += 1
     637          Tkinter.Radiobutton( sequence_frame,
     638                       text="Log:",
     639                       variable=self.sequence_type,
     640                       value="log",
     641                       anchor=Tkinter.W).grid(row=seq_row,column=0,sticky="w")
     642
     643          self.log_start_tk = Tkinter.DoubleVar()
     644          self.log_start_tk.set(-1.0)
     645          self.log_stop_tk = Tkinter.DoubleVar()
     646          self.log_stop_tk.set(2.0)
     647          self.log_n_tk = Tkinter.IntVar()
     648          self.log_n_tk.set(5)
     649
     650          log_frame = Tkinter.Frame( sequence_frame)
     651          log_frame.grid(row=seq_row,column=1)
     652          Tkinter.Label(log_frame,text="start: 10^").grid(row=0,column=0)
     653          Tkinter.Entry(log_frame,textvariable=self.log_start_tk,width=6).grid(row=0,column=1)
     654          Tkinter.Label(log_frame,text="  stop: 10^").grid(row=0,column=2)
     655          Tkinter.Entry(log_frame,textvariable=self.log_stop_tk,width=6).grid(row=0,column=3)
     656          Tkinter.Label(log_frame,text="  N:").grid(row=0,column=4)
     657          Tkinter.Entry(log_frame,textvariable=self.log_n_tk,width=6).grid(row=0,column=5)
     658
     659          seq_row += 1
     660          Tkinter.Radiobutton( sequence_frame,
     661                       text="Log:",
     662                       variable=self.sequence_type,
     663                       value="logb",
     664                       anchor=Tkinter.W).grid(row=seq_row,column=0,sticky="w")
     665
     666          self.logb_start_tk = Tkinter.DoubleVar()
     667          self.logb_start_tk.set(0.1)
     668          self.logb_stop_tk = Tkinter.DoubleVar()
     669          self.logb_stop_tk.set(100.0)
     670          self.logb_n_tk = Tkinter.IntVar()
     671          self.logb_n_tk.set(5)
     672
     673          logb_frame = Tkinter.Frame( sequence_frame)
     674          logb_frame.grid(row=seq_row,column=1)
     675          Tkinter.Label(logb_frame,text="start:").grid(row=0,column=0)
     676          Tkinter.Entry(logb_frame,textvariable=self.logb_start_tk,width=6).grid(row=0,column=1)
     677          Tkinter.Label(logb_frame,text="  stop:").grid(row=0,column=2)
     678          Tkinter.Entry(logb_frame,textvariable=self.logb_stop_tk,width=6).grid(row=0,column=3)
     679          Tkinter.Label(logb_frame,text="  N:").grid(row=0,column=4)
     680          Tkinter.Entry(logb_frame,textvariable=self.logb_n_tk,width=6).grid(row=0,column=5)
     681
     682          # rest duration frame
     683          Tkinter.Label(rest_dur_frame,
     684                        text="Other sequence parameters",
     685                        font=("Helvetica",12,"bold"),).grid(row=0,column=0,columnspan=2)
     686
     687          Tkinter.Label(rest_dur_frame,
     688                        text="Interval duration (seconds)").grid(row=1,column=0)
     689          self.rest_dur = Tkinter.DoubleVar()
     690          self.rest_dur.set(0.5)
     691          Tkinter.Entry(rest_dur_frame,
     692                        textvariable=self.rest_dur,
     693                        width=10).grid(row=1,column=1)
     694
     695          self.shuffle_tk_var = Tkinter.BooleanVar()
     696          self.shuffle_tk_var.set(0)
     697          Tkinter.Checkbutton( rest_dur_frame,
     698                               text="Shuffle sequence order",
     699                               variable=self.shuffle_tk_var).grid(row=2,column=0,columnspan=2)
     700
     701          if self.orig_values is not None:
     702              self.var_name.set( self.orig_values.parameters.variable )
     703
     704              self.sequence_manual_string.set( str(self.orig_values.parameters.sequence) )
     705
     706              self.rest_dur.set( self.orig_values.parameters.rest_duration_sec )
     707
     708
     709 -    def validate(self):
     710          if self.sequence_type.get() == "manual":
     711              try:
     712                  seq = eval(self.sequence_manual_string.get())
     713              except Exception, x:
     714                  tkMessageBox.showwarning("Invalid sequence parameters",
     715                                           "Manual sequence entry: %s"%(str(x),),
     716                                           parent=self)
     717                  return 0
     718              if type(seq) != types.ListType:
     719                  tkMessageBox.showwarning("Invalid sequence parameters",
     720                                           "Manual sequence entry: Not a list",
     721                                           parent=self)
     722                  return 0
     723          elif self.sequence_type.get() == "linear":
     724              start = self.lin_start_tk.get()
     725              stop = self.lin_stop_tk.get()
     726              n = self.lin_n_tk.get()
     727              if n < 2:
     728                  tkMessageBox.showwarning("Invalid sequence parameters",
     729                                           "Must have n >= 2.",
     730                                           parent=self)
     731                  return 0
     732
     733              incr = (stop-start)/float(n-1)
     734              seq = range(n)
     735              for i in range(n):
     736                  seq[i] = i*incr + start
     737          elif self.sequence_type.get() == "log":
     738              start = self.log_start_tk.get()
     739              stop = self.log_stop_tk.get()
     740              n = self.log_n_tk.get()
     741              if n < 2:
     742                  tkMessageBox.showwarning("Invalid sequence parameters",
     743                                           "Must have n >= 2.",
     744                                           parent=self)
     745                  return 0
     746
     747              incr = (stop-start)/float(n-1)
     748              seq = range(n)
     749              for i in range(n):
     750                  seq[i] = 10.0**( i*incr + start )
     751          elif self.sequence_type.get() == "logb":
     752              start = self.logb_start_tk.get()
     753              stop = self.logb_stop_tk.get()
     754              start = math.log10(start)
     755              stop = math.log10(stop)
     756              n = self.logb_n_tk.get()
     757              if n < 2:
     758                  tkMessageBox.showwarning("Invalid sequence parameters",
     759                                           "Must have n >= 2.",
     760                                           parent=self)
     761                  return 0
     762              incr = (stop-start)/float(n-1)
     763              seq = range(n)
     764              for i in range(n):
     765                  seq[i] = 10.0**( i*incr + start )
     766          else:
     767              tkMessageBox.showwarning("Invalid sequence parameters",
     768                                       "Invalid sequence type.",
     769                                       parent=self)
     770              return 0
     771          rest_dur_sec = self.rest_dur.get()
     772
     773          if self.shuffle_tk_var.get():
     774              random.shuffle(seq)
     775
     776          self.result = Loop(variable=self.var_name.get(),
     777                             sequence=seq,
     778                             rest_duration_sec=rest_dur_sec)
     779          return 1
     780
     781 -    def destroy(self):
     782          # clear tk variables
     783          self.var_name = None
     784          self.sequence_type = None
     785          self.sequence_manual_string = None
     786          self.rest_dur = None
     787          # call master's destroy method
     788          tkSimpleDialog.Dialog.destroy(self)
     789
     790 -def get_server(hostname="",port=7766):
     791      class ConnectWindow(Tkinter.Frame):
     792          def __init__(self,master=None,hostname="",port=7766,**kw):
     793              # Allow VisionEgg Tkinter exception window
     794              VisionEgg.config._Tkinter_used = True
     795
     796              Tkinter.Frame.__init__(self,master, **kw)
     797              self.winfo_toplevel().title("EPhysGUI Connect - Vision Egg")
     798              current_row = 0
     799              Tkinter.Message(self,\
     800                            text='Welcome to the "EPhys GUI" of the Vision Egg!\n\n'+\
     801                            'Please enter the hostname '+\
     802                            'and port number '+\
     803                            'of the computer on which you have the '+\
     804                            '"EPhys server" running.').grid(row=current_row,column=0,columnspan=2)
     805              hostname = 'localhost'
     806
     807              self.hostname_tk = Tkinter.StringVar()
     808              self.hostname_tk.set(hostname)
     809              current_row += 1
     810              Tkinter.Label(self,text="Hostname:").grid(row=current_row, column=0)
     811              Tkinter.Entry(self,textvariable=self.hostname_tk).grid(row=current_row, column=1)
     812
     813              self.port_tk = Tkinter.IntVar()
     814              self.port_tk.set(port)
     815              current_row += 1
     816              Tkinter.Label(self,text="Port:").grid(row=current_row, column=0)
     817              Tkinter.Entry(self,textvariable=self.port_tk).grid(row=current_row, column=1)
     818
     819              current_row += 1
     820              bf = Tkinter.Frame(self)
     821              bf.grid(row=current_row,column=0,columnspan=2)
     822              ok=Tkinter.Button(bf,text="OK",command=self.ok)
     823              ok.grid(row=0,column=0)
     824              ok.focus_force()
     825              ok.bind('<Return>',self.ok)
     826              Tkinter.Button(bf,text="Cancel",command=self.quit).grid(row=0,column=1)
     827              self.result = None
     828
     829          def ok(self,dummy_arg=None):
     830              self.result = (self.hostname_tk.get(),self.port_tk.get())
     831              self.destroy()
     832              self.quit()
     833
     834      connect_win = ConnectWindow(hostname=hostname,port=port)
     835      connect_win.pack()
     836      connect_win.mainloop()
     837      return connect_win.result
     838
     839 -class GammaFrame(Tkinter.Frame):
     840 -    def __init__(self,
     841                   master=None,
     842                   ephys_server=None,**kw):
     843          Tkinter.Frame.__init__(self,master,**kw)
     844          self.winfo_toplevel().title("Gamma - Vision Egg")
     845          self.ephys_server = ephys_server
     846
     847          self.columnconfigure(0,weight=1)
     848
     849          row = 0
     850          Tkinter.Label(self,
     851                        font=("Helvetica",12,"bold"),
     852                        text="Load Gamma Table").grid(row=row)
     853
     854          row += 1
     855          Tkinter.Button(self,
     856                         text="Set from .ve_gamma file...",
     857                         command=self.set_from_file).grid(row=row,sticky="w")
     858
     859          row += 1
     860          Tkinter.Button(self,
     861                         text="Set to monitor default (linear gamma table)",
     862                         command=self.set_monitor_default).grid(row=row,sticky="w")
     863
     864          row += 1
     865          invert_frame = Tkinter.Frame(self)
     866          invert_frame.grid(row=row,sticky="we")
     867
     868          Tkinter.Button(invert_frame,
     869                         text="Linearize luminance for gammas",
     870                         command=self.linearize).grid(row=0,column=0)
     871
     872          Tkinter.Label(invert_frame,
     873                        text="Red:").grid(row=0,column=1)
     874
     875          self.red_gamma = Tkinter.DoubleVar()
     876          self.red_gamma.set(2.2)
     877
     878          Tkinter.Entry(invert_frame,
     879                        textvariable=self.red_gamma,
     880                        width=5).grid(row=0,column=2)
     881
     882          Tkinter.Label(invert_frame,
     883                        text="Green:").grid(row=0,column=3)
     884
     885          self.green_gamma = Tkinter.DoubleVar()
     886          self.green_gamma.set(2.2)
     887
     888          Tkinter.Entry(invert_frame,
     889                        textvariable=self.green_gamma,
     890                        width=5).grid(row=0,column=4)
     891
     892          Tkinter.Label(invert_frame,
     893                        text="Blue:").grid(row=0,column=5)
     894
     895          self.blue_gamma = Tkinter.DoubleVar()
     896          self.blue_gamma.set(2.2)
     897
     898          Tkinter.Entry(invert_frame,
     899                        textvariable=self.blue_gamma,
     900                        width=5).grid(row=0,column=6)
     901
     902          row += 1
     903          self.success_label = Tkinter.Label(self)
     904          self.success_label.grid(row=row)
     905
     906 -    def get_corrected_gamma_table(self,gamma):
     907          # c is a constant scale factor.  It is always 1.0 when
     908          # luminance is normalized to range [0.0,1.0] and input units
     909          # in range [0.0,1.0], as is OpenGL standard.
     910          c = 1.0
     911          inc = 1.0/255
     912          target_luminances = numpy.arange(0.0,1.0+inc,inc)
     913          output_ramp = numpy.zeros(target_luminances.shape,dtype=numpy.int32)
     914          for i in range(len(target_luminances)):
     915              L = target_luminances[i]
     916              if L == 0.0:
     917                  v_88fp = 0
     918              else:
     919                  v = math.exp( (math.log(L) - math.log(c)) /gamma)
     920                  v_88fp = int(round((v*255) * 256)) # convert to from [0.0,1.0] floating point to [0.0,255.0] 8.8 fixed point
     921              output_ramp[i] = v_88fp # 8.8 fixed point format
     922          return list(output_ramp) # convert to Python list
     923
     924 -    def linearize(self, dummy_arg=None):
     925          self.success_label.configure(text="Setting...")
     926          try:
     927              red = self.get_corrected_gamma_table(self.red_gamma.get())
     928              green = self.get_corrected_gamma_table(self.green_gamma.get())
     929              blue = self.get_corrected_gamma_table(self.blue_gamma.get())
     930          except:
     931              self.success_label.configure(text="Calculation error")
     932              raise
     933          try:
     934              if self.ephys_server.set_gamma_ramp(red,green,blue):
     935                  self.success_label.configure(text="Success")
     936              else:
     937                  self.success_label.configure(text="Failed: Invalid gamma values?")
     938          except Exception,x:
     939              self.success_label.configure(text="Failed: %s: %s"%(x.__class__,str(x)))
     940              raise
     941
     942 -    def set_monitor_default(self, dummy_arg=None):
     943          self.success_label.configure(text="Setting...")
     944          try:
     945              red = self.get_corrected_gamma_table(1.0) # linear gamma table
     946          except:
     947              self.success_label.configure(text="Calculation error")
     948              raise
     949          green = red
     950          blue = red
     951          try:
     952              if self.ephys_server.set_gamma_ramp(red,green,blue):
     953                  self.success_label.configure(text="Success")
     954              else:
     955                  self.success_label.configure(text="Failed: Invalid gamma values?")
     956          except Exception,x:
     957              self.success_label.configure(text="Failed: %s: %s"%(x.__class__,str(x)))
     958              raise
     959
     960 -    def set_from_file(self):
     961          self.success_label.configure(text="Setting...")
     962          filename = tkFileDialog.askopenfilename(
     963              parent=self,
     964              defaultextension=".ve_gamma",
     965              filetypes=[('Configuration file','*.ve_gamma')],
     966              initialdir=VisionEgg.config.VISIONEGG_USER_DIR)
     967          if not filename:
     968              self.success_label.configure(text="No file given")
     969              return
     970          fd = open(filename,"r")
     971          gamma_values = []
     972          for line in fd.readlines():
     973              line = line.strip() # remove leading/trailing whitespace
     974              if line.startswith("#"): # comment, ignore
     975                  continue
     976              try:
     977                  gamma_values.append( map(int, line.split() ) )
     978              except Exception, x:
     979                  self.success_label.configure(text="File error")
     980                  raise
     981              if len(gamma_values[-1]) != 3:
     982                  self.success_label.configure(text="File error")
     983                  raise RuntimeError("expected 3 values per gamma entry")
     984          if len(gamma_values) != 256:
     985              self.success_label.configure(text="File error")
     986              raise RuntimeError("expected 256 gamma entries")
     987          red, green, blue = zip(*gamma_values)
     988          try:
     989              if self.ephys_server.set_gamma_ramp(red,green,blue):
     990                  self.success_label.configure(text="Success")
     991              else:
     992                  self.success_label.configure(text="Failed: Invalid gamma values?")
     993          except Exception,x:
     994              self.success_label.configure(text="Failed: %s: %s"%(x.__class__,str(x)))
     995              raise
     996
     997 -class ImageSequenceLauncher(Tkinter.Toplevel):
     998 -    def __init__(self,master=None,ephys_server=None,**cnf):
     999          Tkinter.Toplevel.__init__(self,master,**cnf)
    1000          if ephys_server is None:
    1001              raise ValueError("Must specify ephys_server")
    1002          self.ephys_server = ephys_server
    1003
    1004          self.columnconfigure(1,weight=1)
    1005
    1006          row = 0
    1007          Tkinter.Label(self,text="Frames per second").grid(row=row,column=0)
    1008          self.fps_var = Tkinter.DoubleVar()
    1009          self.fps_var.set(12.0)
    1010          Tkinter.Entry(self,textvariable=self.fps_var).grid(row=row,column=1,sticky="we")
    1011          row += 1
    1012          Tkinter.Label(self,text="Filename base").grid(row=row,column=0)
    1013          self.filename_base = Tkinter.StringVar()
    1014          self.filename_base.set("im")
    1015          Tkinter.Entry(self,textvariable=self.filename_base).grid(row=row,column=1,sticky="we")
    1016          row += 1
    1017          Tkinter.Label(self,text="Filename suffix").grid(row=row,column=0)
    1018          self.filename_suffix = Tkinter.StringVar()
    1019          self.filename_suffix.set(".tif")
    1020          Tkinter.Entry(self,textvariable=self.filename_suffix).grid(row=row,column=1,sticky="we")
    1021          row += 1
    1022          Tkinter.Label(self,text="Save directory on server").grid(row=row,column=0)
    1023          self.server_save_dir = Tkinter.StringVar()
    1024          server_dir = self.ephys_server.get_cwd()
    1025          self.server_save_dir.set(server_dir)
    1026          Tkinter.Entry(self,textvariable=self.server_save_dir).grid(row=row,column=1,sticky="we")
    1027          row += 1
    1028          Tkinter.Button(self,text="Save movie",command=self.do_it).grid(row=row,column=0,columnspan=2)
    1029          self.focus_set()
    1030          self.grab_set()
    1031 -    def do_it(self):
    1032          fps = self.fps_var.get()
    1033          filename_base = self.filename_base.get()
    1034          filename_suffix = self.filename_suffix.get()
    1035          server_save_dir = self.server_save_dir.get()
    1036          self.ephys_server.save_image_sequence(fps=fps,
    1037                                                filename_base=filename_base,
    1038                                                filename_suffix=filename_suffix,
    1039                                                save_dir=server_save_dir)
    1040          self.destroy()
    1041
    1042 -class AppWindow(Tkinter.Frame):
    1043 -    def __init__(self,
    1044                   master=None,
    1045                   client_list=None,
    1046                   server_hostname='',
    1047                   server_port=7766,
    1048                   **cnf):
    1049
    1050          if hasattr(VisionEgg, '_exception_hook_keeper'):
    1051              # Keep original exception handler
    1052              self._orig_report_callback_exception = Tkinter.Tk.report_callback_exception
    1053              self._tk = Tkinter.Tk
    1054              # Use Vision Egg exception handler
    1055              Tkinter.Tk.report_callback_exception = VisionEgg._exception_hook_keeper.handle_exception
    1056
    1057          # Allow VisionEgg Tkinter exception window
    1058          VisionEgg.config._Tkinter_used = True
    1059
    1060          # create myself
    1061          Tkinter.Frame.__init__(self,master, **cnf)
    1062          self.winfo_toplevel().title("EPhysGUI - Vision Egg")
    1063
    1064          self.client_list = client_list
    1065
    1066          self.server_hostname = server_hostname
    1067          self.server_port = server_port
    1068
    1069          self.pyro_client = VisionEgg.PyroClient.PyroClient(self.server_hostname,self.server_port)
    1070          self.ephys_server = self.pyro_client.get("ephys_server")
    1071          self.ephys_server.first_connection()
    1072
    1073          self.stim_onset_cal_tk_var = Tkinter.BooleanVar()
    1074          self.stim_onset_cal_tk_var.set(0)
    1075
    1076          self.autosave_dir = Tkinter.StringVar()
    1077          self.autosave_dir.set( os.path.abspath(os.curdir) )
    1078
    1079          self.autosave_basename = Tkinter.StringVar()
    1080
    1081          # create menu bar
    1082          self.bar = Tkinter.Menu(tearoff=0)
    1083          top = self.winfo_toplevel()
    1084          top.configure(menu=self.bar)
    1085
    1086          self.bar.file_menu = Tkinter.Menu(self.bar, name="file_menu")
    1087          self.bar.add_cascade(label="File",menu=self.bar.file_menu)
    1088
    1089          self.bar.file_menu.add_command(label='Save image sequence...', command=self.save_image_sequence)
    1090          self.bar.file_menu.add_command(label='Save configuration file...', command=self.save_config)
    1091          self.bar.file_menu.add_command(label='Load configuration file...', command=self.load_config)
    1092          self.bar.file_menu.add_command(label='Load auto-saved .py parameter file...', command=self.load_params)
    1093          self.bar.file_menu.add_separator()
    1094          self.bar.file_menu.add_command(label='Load Vision Egg script...', command=self.load_demoscript)
    1095          self.bar.file_menu.add_separator()
    1096
    1097          self.quit_server_too = Tkinter.BooleanVar()
    1098          self.quit_server_too.set(1)
    1099          self.bar.file_menu.add_checkbutton(label='Quit server too',
    1100                                             variable=self.quit_server_too)
    1101          self.bar.file_menu.add_command(label='Quit',
    1102                                         command=self.quit,
    1103                                         )
    1104
    1105          stimkey = self.ephys_server.get_stimkey()
    1106          self.stimulus_tk_var = Tkinter.StringVar()
    1107          self.stimulus_tk_var.set( stimkey )
    1108
    1109          self.bar.stimuli_menu = Tkinter.Menu(self.bar, name="stimuli_menu")
    1110          self.bar.add_cascade(label="Stimuli",menu=self.bar.stimuli_menu)
    1111          for maybe_stimkey, maybe_control_frame, maybe_title in self.client_list:
    1112              if maybe_title != "Vision Egg Script":
    1113                  self.bar.stimuli_menu.add_radiobutton(label=maybe_title,
    1114                                                        command=self.change_stimulus,
    1115                                                        variable=self.stimulus_tk_var,
    1116                                                        value=maybe_stimkey)
    1117
    1118          self.bar.calibration_menu = Tkinter.Menu(self.bar, name="calibration_menu")
    1119          self.bar.add_cascade(label="Configure/Calibrate",
    1120                               menu=self.bar.calibration_menu)
    1121
    1122          self.bar.calibration_menu.add_command(label='3D Perspective...', command=self.launch_screen_pos)
    1123          self.bar.calibration_menu.add_command(label='Stimulus onset timing...', command=self.launch_stim_onset_cal)
    1124          self.bar.calibration_menu.add_command(label='Load gamma table...', command=self.launch_gamma_panel)
    1125          self.notify_on_dropped_frames = Tkinter.BooleanVar()
    1126          self.notify_on_dropped_frames.set(1)
    1127          self.bar.calibration_menu.add_checkbutton(label='Warn on frame skip',
    1128                                                    variable=self.notify_on_dropped_frames)
    1129
    1130          self.override_t_abs_sec = Tkinter.StringVar() # Tkinter DoubleVar loses precision
    1131          self.override_t_abs_sec.set("0.0")
    1132
    1133          self.override_t_abs_on = Tkinter.BooleanVar()
    1134          self.override_t_abs_on.set(0)
    1135          self.bar.calibration_menu.add_checkbutton(label='Override server absolute time (CAUTION)',
    1136                                                    variable=self.override_t_abs_on)
    1137
    1138          row = 0
    1139
    1140          # options for self.stim_frame in grid layout manager
    1141          self.stim_frame_cnf = {'row':row,
    1142                                 'column':0,
    1143                                 'columnspan':2,
    1144                                 'sticky':'nwes'}
    1145
    1146          row += 1
    1147          Tkinter.Label(self,
    1148                        text="Sequence information",
    1149                        font=("Helvetica",12,"bold")).grid(row=row,column=0)
    1150          row += 1
    1151          # options for self.loop_frame in grid layout manager
    1152          self.loop_frame_cnf = {'row':row,
    1153                                 'column':0,
    1154                                 'sticky':'nwes'}
    1155
    1156          row -= 1
    1157          Tkinter.Label(self,
    1158                        text="Parameter Save Options",
    1159                        font=("Helvetica",12,"bold")).grid(row=row,column=1)
    1160          row += 1
    1161          self.auto_save_frame = Tkinter.Frame(self)
    1162          asf = self.auto_save_frame # shorthand
    1163          asf.grid(row=row,column=1,sticky="nwes")
    1164          asf.columnconfigure(1,weight=1)
    1165
    1166          asf.grid_row = 0
    1167          self.autosave = Tkinter.BooleanVar()
    1168          self.autosave.set(1)
    1169          self.auto_save_button = Tkinter.Checkbutton(asf,
    1170                                                      text="Auto save trial parameters",
    1171                                                      variable=self.autosave)
    1172          self.auto_save_button.grid(row=asf.grid_row,column=0,columnspan=2)
    1173
    1174          self.param_file_type_tk_var = Tkinter.StringVar()
    1175          self.param_file_type_tk_var.set("Python format")
    1176          filetype_bar = Tkinter.Menubutton(asf,
    1177                                   textvariable=self.param_file_type_tk_var,
    1178                                   relief=Tkinter.RAISED)
    1179          filetype_bar.grid(row=asf.grid_row,column=2)
    1180          filetype_bar.menu = Tkinter.Menu(filetype_bar,tearoff=0)
    1181          filetype_bar.menu.add_radiobutton(label="Python format",
    1182                                   value="Python format",
    1183                                   variable=self.param_file_type_tk_var)
    1184          filetype_bar.menu.add_radiobutton(label="Matlab format",
    1185                                   value="Matlab format",
    1186                                   variable=self.param_file_type_tk_var)
    1187          filetype_bar['menu'] = filetype_bar.menu
    1188
    1189          asf.grid_row += 1
    1190          Tkinter.Label(asf,
    1191                        text="Parameter file directory:").grid(row=asf.grid_row,column=0,sticky="e")
    1192          Tkinter.Entry(asf,
    1193                        textvariable=self.autosave_dir).grid(row=asf.grid_row,column=1,sticky="we")
    1194          Tkinter.Button(asf,
    1195                         text="Set...",command=self.set_autosave_dir).grid(row=asf.grid_row,column=2)
    1196          asf.grid_row += 1
    1197          Tkinter.Label(asf,
    1198                        text="Parameter file basename:").grid(row=asf.grid_row,column=0,sticky="e")
    1199          Tkinter.Entry(asf,
    1200                        textvariable=self.autosave_basename).grid(row=asf.grid_row,column=1,sticky="we")
    1201          Tkinter.Button(asf,
    1202                         text="Reset",command=self.reset_autosave_basename).grid(row=asf.grid_row,column=2)
    1203
    1204          row += 1
    1205          Tkinter.Button(self, text='Do single trial', command=self.do_single_trial).grid(row=row,column=0)
    1206          Tkinter.Button(self, text='Do sequence', command=self.do_loops).grid(row=row,column=1)
    1207
    1208          row += 1
    1209          self.progress = VisionEgg.GUI.ProgressBar(self,
    1210                                                    width=300,
    1211                                                    relief="sunken",
    1212                                                    doLabel=0,
    1213                                                    labelFormat="%s")
    1214          self.progress.labelText = "Starting..."
    1215          self.progress.updateProgress(0)
    1216          self.progress.grid(row=row,column=0,columnspan=2)#,sticky='we')
    1217
    1218          # Allow rows and columns to expand
    1219          for i in range(2):
    1220              self.columnconfigure(i,weight=1)
    1221          for i in range(row+1):
    1222              self.rowconfigure(i,weight=1)
    1223
    1224          self.switch_to_stimkey( stimkey )
    1225
    1226          self.config_dir = None
    1227          self.demoscript_filename = None
    1228          self.vars_list = None
    1229
    1230 -    def __del__( self ):
    1231          if hasattr(self,'_orig_report_callback_exception'):
    1232              self._tk.report_callback_exception = self._orig_report_callback_exception
    1233
    1234 -    def switch_to_stimkey( self, stimkey ):
    1235          success = 0
    1236          for maybe_stimkey, maybe_control_frame, maybe_title in self.client_list:
    1237              if stimkey == maybe_stimkey:
    1238                  control_frame_klass = maybe_control_frame
    1239                  success = 1
    1240
    1241          if not success:
    1242              raise RuntimeError("Could not find valid client for server stimkey %s"%stimkey)
    1243
    1244          if hasattr(self, 'stim_frame'):
    1245              # clear old frame
    1246              self.stim_frame.destroy()
    1247              del self.stim_frame
    1248
    1249          self.stim_frame = control_frame_klass(self,suppress_go_buttons=1)
    1250          if stimkey == "dropin_server":
    1251              self.stim_frame.gen_var_widgets(self.demoscript_filename, self.vars_list)
    1252          self.stim_frame.connect(self.server_hostname,self.server_port)
    1253          self.stim_frame.grid( **self.stim_frame_cnf )
    1254
    1255          global loopable_variables
    1256          loopable_variables = self.stim_frame.get_loopable_variable_names()
    1257          if hasattr(self, 'loop_frame'):
    1258              # clear old frame
    1259              self.loop_frame.destroy()
    1260              del self.loop_frame
    1261          self.loop_frame = ScrollListFrame(master=self,
    1262                                            container_class=LoopContainedObject)
    1263          self.loop_frame.grid( **self.loop_frame_cnf )
    1264
    1265          self.autosave_basename.set( self.stim_frame.get_shortname() )
    1266
    1267          self.stimulus_tk_var.set( self.stim_frame.get_shortname() ) # set menuitem
    1268
    1269          self.progress.labelText = "Ready"
    1270          self.progress.updateProgress(0)
    1271
    1272 -    def change_stimulus(self, dummy_arg=None, new_stimkey=None ):
    1273          # if new_stimkey is None, get from the tk variable
    1274          if new_stimkey is None:
    1275              new_stimkey = self.stimulus_tk_var.get()
    1276
    1277          found = 0
    1278          for maybe_stimkey, maybe_control_frame, maybe_title in self.client_list:
    1279              if new_stimkey == maybe_stimkey:
    1280                  new_control_frame_klass = maybe_control_frame
    1281                  new_stimkey = maybe_stimkey
    1282                  found = 1
    1283                  break
    1284
    1285          if not found:
    1286              raise RuntimeError("Don't know about stimkey %s"%new_stimkey)
    1287
    1288          if new_control_frame_klass != self.stim_frame.__class__ or new_stimkey == "dropin_server":
    1289              # make wait cursor
    1290              root = self.winfo_toplevel()
    1291              old_cursor = root["cursor"]
    1292              root["cursor"] = "watch"
    1293              root.update()
    1294              try:
    1295
    1296                  self.progress.labelText = "Changing stimulus..."
    1297                  self.progress.updateProgress(0)
    1298
    1299                  self.ephys_server.set_next_stimkey( new_stimkey )
    1300
    1301                  # new stimulus type
    1302                  self.stim_frame.quit_server() # disconnect
    1303
    1304                  # in case of loaded Vision Egg script, quit_server()
    1305                  # sends a signal to stop the "wait" loop, WITHOUT
    1306                  # raising a flag to run the script
    1307
    1308                  self.ephys_server.get_stimkey() # wait for server to load
    1309
    1310                  self.switch_to_stimkey( new_stimkey)
    1311              finally:
    1312                  #restore cursor
    1313                  root["cursor"] = old_cursor
    1314                  root.update()
    1315
    1316 -    def save_image_sequence(self):
    1317          ImageSequenceLauncher(self,ephys_server=self.ephys_server)
    1318
    1319 -    def save_config(self):
    1320          self.stim_frame.send_values() # copy values from Tkinter to self.stim_frame.meta_params and send to server
    1321          if self.config_dir is not None:
    1322              initialdir = self.config_dir
    1323          else:
    1324              initialdir = VisionEgg.config.VISIONEGG_USER_DIR
    1325          filename = tkFileDialog.asksaveasfilename(
    1326              parent=self,
    1327              defaultextension=".ve_cfg",
    1328              filetypes=[('Configuration file','*.ve_cfg')],
    1329              initialdir=initialdir)
    1330          if not filename:
    1331              return
    1332          fd = open(filename,"wb")
    1333          save_dict = {'stim_type':self.stim_frame.get_shortname(),
    1334                       'loop_list':self.loop_frame.list,
    1335                       'stim_frame_dict':self.stim_frame.get_parameters_dict(),
    1336                       'autosave':self.autosave.get(),
    1337                       'autosave_dir':self.autosave_dir.get(),
    1338                       'autosave_basename':self.autosave_basename.get()}
    1339          pickle.dump( save_dict, fd )
    1340          self.config_dir = os.path.split(filename)[0] # get dir
    1341
    1342 -    def load_config(self):
    1343          if self.config_dir is not None:
    1344              initialdir = self.config_dir
    1345          else:
    1346              initialdir = VisionEgg.config.VISIONEGG_USER_DIR
    1347          filename = tkFileDialog.askopenfilename(
    1348              parent=self,
    1349              defaultextension=".ve_cfg",
    1350              filetypes=[('Configuration file','*.ve_cfg')],
    1351              initialdir=initialdir,
    1352              )
    1353          if not filename:
    1354              return
    1355          self.config_dir = os.path.split(filename)[0] # get dir
    1356          fd = open(filename,"rb")
    1357          file_contents = fd.read()
    1358          file_contents = file_contents.replace('\r\n','\n') # deal with Windows newlines
    1359          memory_file = StringIO.StringIO(file_contents)
    1360          load_dict = pickle.load(memory_file)
    1361          if load_dict['stim_type'] != self.stim_frame.get_shortname():
    1362              self.change_stimulus(new_stimkey=load_dict['stim_type']+"_server")
    1363          self.loop_frame.list = load_dict['loop_list']
    1364          self.loop_frame.update_now()
    1365          self.stim_frame.set_parameters_dict( load_dict['stim_frame_dict'] )
    1366          self.autosave.set(load_dict['autosave'])
    1367          self.autosave_dir.set(load_dict['autosave_dir'])
    1368          self.autosave_basename.set(load_dict['autosave_basename'])
    1369
    1370          self.stim_frame.update_tk_vars()
    1371
    1372 -    def load_params(self,orig_load_dict={}):
    1373          filename = tkFileDialog.askopenfilename(
    1374              parent=self,
    1375              defaultextension=".py",
    1376              filetypes=[('Auto-saved parameter file','*.py')])
    1377          if not filename:
    1378              return
    1379          locals = {}
    1380          load_dict = orig_load_dict.copy() # make copy of default values
    1381          execfile(filename,locals,load_dict) # execute the file
    1382          if load_dict['stim_type'] != self.stim_frame.get_shortname():
    1383              self.change_stimulus(new_stimkey=load_dict['stim_type']+"_server")
    1384          self.loop_frame.list = [] # clear loop list
    1385          self.loop_frame.update_now()
    1386          new_params = {}
    1387          exception_info = []
    1388          for param_name in dir(self.stim_frame.meta_params):
    1389              if param_name[:2] != "__":
    1390                  try:
    1391                      new_params[param_name] = load_dict[param_name]
    1392                  except Exception, x:
    1393                      exception_info.append(sys.exc_info()) # don't die on exception
    1394                  else:
    1395                      del load_dict[param_name]
    1396          for exc_type, exc_value, exc_traceback in exception_info:
    1397              # ignore actual traceback
    1398              VisionEgg.GUI.showexception(exc_type,exc_value,"")
    1399          self.stim_frame.set_parameters_dict( new_params )
    1400          self.autosave_basename.set(load_dict['stim_type'])
    1401
    1402          try:
    1403              override_t_abs_sec = load_dict['go_loop_start_time_abs_sec']
    1404          except Exception, x:
    1405              tkMessageBox.showwarning("No absolute time parameter",
    1406                                       "No absolute time parameter when loading file",
    1407                                       parent=self)
    1408          else:
    1409              if not self.override_t_abs_on.get():
    1410                  self.override_t_abs_on.set(1)
    1411                  tkMessageBox.showwarning("Overriding absolute time parameter",
    1412                                           "Overriding absolute time parameter on server. "+\
    1413                                           "Remember to turn this option off (in Configure "+\
    1414                                           "/ Calibrate menu) when done.",
    1415                                           parent=self)
    1416              self.override_t_abs_sec.set(repr(override_t_abs_sec)) # make string
    1417
    1418          self.stim_frame.update_tk_vars()
    1419          return load_dict # return unused variables
    1420
    1421 -    def load_demoscript(self):
    1422          self.demoscript_filename = tkFileDialog.askopenfilename(
    1423              parent=self,
    1424              defaultextension=".py",
    1425              filetypes=[('Vision Egg Demo Script','*.py')])
    1426
    1427          if not self.demoscript_filename:
    1428              return
    1429          else:
    1430              # make wait cursor
    1431              root = self.winfo_toplevel()
    1432              old_cursor = root["cursor"]
    1433              root["cursor"] = "watch"
    1434              root.update()
    1435              try:
    1436                  fd1 = open(self.demoscript_filename, 'r')
    1437                  demoscript = ""
    1438
    1439                  # List of all variables.
    1440                  # Each variable is a tuple of the form [x, y, z] where:
    1441                  # x: variable type ID,
    1442                  # y: variable name,
    1443                  # z: variable value.
    1444                  self.vars_list = []
    1445
    1446                  lines = fd1.read().splitlines()
    1447                  keep_lines = []
    1448                  while len(lines):
    1449                      myString = lines.pop(0)
    1450                      if myString.find("get_default_screen()") != -1:
    1451                          append_flag = 0
    1452                      elif myString.find("watch_exceptions()") != -1:
    1453                          append_flag = 0
    1454                      elif myString.find("start_default_logging()") != -1:
    1455                          append_flag = 0
    1456                      elif myString.find("#%f") == 0:
    1457                          self.vars_list.append([VarTypes.getID("float"), myString.replace("#%f", "").split()[0]])
    1458                          append_flag = 0
    1459                      elif myString.find("#%i") == 0:
    1460                          self.vars_list.append([VarTypes.getID("integer"), myString.replace("#%i", "").split()[0]])
    1461                          append_flag = 0
    1462                      elif myString.find("#%s") == 0:
    1463                          self.vars_list.append([VarTypes.getID("string"), myString.replace("#%s", "").split()[0]])
    1464                          append_flag = 0
    1465                      else:
    1466                          append_flag = 1
    1467
    1468                      if append_flag == 1:
    1469                          keep_lines.append( myString )
    1470
    1471                  fd1.close()
    1472                  demoscript = '\n'.join(keep_lines)
    1473
    1474                  # Client side AST. Only used to extract default values
    1475                  # of elected variables:
    1476                  try:
    1477                      AST = parser.suite(demoscript)
    1478
    1479                      for var in self.vars_list:
    1480                          var_val = AST_ext.extract_from_AST(AST, var[1])
    1481                          if var[0] == VarTypes.getID("string"):
    1482                              var_val = var_val[1:len(var_val) - 1]
    1483                          var.append(var_val)
    1484
    1485                      del AST # save memory
    1486
    1487                      # unfortunately, sending an AST object over a Pyro
    1488                      # connection has its complications... so we don't
    1489                      # do it
    1490                      self.ephys_server.build_AST(demoscript)
    1491
    1492                      self.change_stimulus(new_stimkey="dropin_server")
    1493
    1494                  except (parser.ParserError, SyntaxError):
    1495                      tkMessageBox.showerror("Error", "Invalid demo script!")
    1496                      err_fd = file('/home/astraw/tmp/err.py',mode='w')
    1497                      err_fd.write(demoscript)
    1498              finally:
    1499                  #restore cursor
    1500                  root["cursor"] = old_cursor
    1501                  root.update()
    1502
    1503 -    def launch_screen_pos(self, dummy_arg=None):
    1504          dialog = Tkinter.Toplevel(self)
    1505          frame = VisionEgg.PyroApps.ScreenPositionGUI.ScreenPositionControlFrame(dialog,
    1506                                                                                  auto_connect=1,
    1507                                                                                  server_hostname=self.server_hostname,
    1508                                                                                  server_port=self.server_port)
    1509          frame.winfo_toplevel().title("3D Calibration - Vision Egg")
    1510          frame.pack(expand=1,fill=Tkinter.BOTH)
    1511
    1512 -    def launch_stim_onset_cal(self, dummy_arg=None):
    1513          dialog = Tkinter.Toplevel(self)
    1514          frame = Tkinter.Frame(dialog)
    1515          frame.winfo_toplevel().title("Timing Calibration - Vision Egg")
    1516          Tkinter.Label(frame,
    1517                        font=("Helvetica",12,"bold"),
    1518                        text="Stimulus onset timing").grid(row=0,column=0)
    1519          Tkinter.Label(frame,
    1520                        text="Use a light detector to verify the onset of a trial."
    1521                        ).grid(row=1,column=0)
    1522          Tkinter.Checkbutton( frame,
    1523                               text="Black box (always) with white box (during trial)",
    1524                               variable=self.stim_onset_cal_tk_var,
    1525                               command=self.update_stim_onset_cal).grid(row=2,column=0)
    1526
    1527          x,y,width,height = self.ephys_server.get_stim_onset_cal_location()
    1528
    1529          location_frame = Tkinter.Frame(frame)
    1530          location_frame.grid(row=3,column=0)
    1531          self.stim_onset_x = Tkinter.DoubleVar()
    1532          self.stim_onset_x.set(x)
    1533          self.stim_onset_y = Tkinter.DoubleVar()
    1534          self.stim_onset_y.set(y)
    1535          self.stim_onset_width = Tkinter.DoubleVar()
    1536          self.stim_onset_width.set(width)
    1537          self.stim_onset_height = Tkinter.DoubleVar()
    1538          self.stim_onset_height.set(height)
    1539
    1540          Tkinter.Label( location_frame, text="Center X:").grid(row=0,column=0)
    1541          Tkinter.Entry( location_frame, textvariable=self.stim_onset_x,width=5).grid(row=0,column=1)
    1542          Tkinter.Label( location_frame, text="Center Y:").grid(row=0,column=2)
    1543          Tkinter.Entry( location_frame, textvariable=self.stim_onset_y,width=5).grid(row=0,column=3)
    1544          Tkinter.Label( location_frame, text="Width:").grid(row=1,column=0)
    1545          Tkinter.Entry( location_frame, textvariable=self.stim_onset_width,width=5).grid(row=1,column=1)
    1546          Tkinter.Label( location_frame, text="Height:").grid(row=1,column=2)
    1547          Tkinter.Entry( location_frame, textvariable=self.stim_onset_height,width=5).grid(row=1,column=3)
    1548
    1549          Tkinter.Button( frame,
    1550                          text="update position and size",
    1551                          command=self.set_stim_onset_cal_position).grid(row=4,column=0)
    1552          self.set_stim_onset_cal_position() # call it once to send server our initial values
    1553          frame.pack(expand=1,fill=Tkinter.BOTH)
    1554
    1555 -    def launch_gamma_panel(self, dummy_arg=None):
    1556          dialog = Tkinter.Toplevel(self)
    1557          frame = GammaFrame(dialog,
    1558                             self.ephys_server)
    1559          frame.pack(expand=1,fill=Tkinter.BOTH)
    1560
    1561 -    def set_autosave_dir(self):
    1562          self.autosave_dir.set( os.path.abspath( tkFileDialog.askdirectory() ) )
    1563
    1564 -    def reset_autosave_basename(self):
    1565          self.autosave_basename.set( self.stim_frame.get_shortname() )
    1566
    1567 -    def update_stim_onset_cal(self, dummy_arg=None):
    1568          on = self.stim_onset_cal_tk_var.get()
    1569          self.ephys_server.set_stim_onset_cal(on)
    1570
    1571 -    def set_stim_onset_cal_position(self, dummy_arg=None):
    1572          x = self.stim_onset_x.get()
    1573          y = self.stim_onset_y.get()
    1574          width = self.stim_onset_width.get()
    1575          height = self.stim_onset_height.get()
    1576          self.ephys_server.set_stim_onset_cal_location(center=(x,y),size=(width,height))
    1577
    1578
    1579 -    def do_loops(self):
    1580          super_loop_list = self.loop_frame.get_list_uncontained()
    1581          # "super_loop_list" is a list of lists, each of which, a
    1582          # "loop_list", contains "loop" class objects
    1583          # eg. [[a], [b, c], [d]]
    1584          # Need to test that "get_list_uncontained()" is returning a list of lists!
    1585          global need_rest_period
    1586          need_rest_period = 0
    1587
    1588          if not len(super_loop_list):
    1589              return
    1590
    1591          ############################################################
    1592          def process_loops(depth): # recursive processing of loops
    1593
    1594              class LoopInfoFrame(Tkinter.Frame):
    1595                  def __init__(self, master=None, **kw):
    1596                      Tkinter.Frame.__init__(self,master,**kw)
    1597                      Tkinter.Label(self,
    1598                          text="Doing sequence").grid(row=0,column=0)
    1599                      self.status_tk_var = Tkinter.StringVar()
    1600                      Tkinter.Label(self,
    1601                          textvariable = self.status_tk_var).grid(row=1, column=0)
    1602                      self.cancel_asap = 0
    1603                      Tkinter.Button(self,
    1604                          text="Cancel",command=self.cancel).grid(row=2,column=0)
    1605                      self.focus_set()
    1606                      self.grab_set()
    1607                  def cancel(self, dummy_arg=None):
    1608                      self.cancel_asap = 1
    1609
    1610              global need_rest_period
    1611
    1612              global loop_info_frame
    1613              if depth == 0: # only make one LoopInfoFrame (when depth is 0, ie. first time)
    1614                  top = Tkinter.Toplevel(self)
    1615                  loop_info_frame = LoopInfoFrame(top)
    1616                  loop_info_frame.pack()
    1617
    1618              loop_list = super_loop_list[depth]
    1619              #print "current depth"
    1620              #print depth
    1621              #print "Loop?"
    1622              #print loop
    1623              max_depth = len(super_loop_list)-1
    1624              #print "max_depth"
    1625              #print max_depth
    1626
    1627              loop = loop_list[0].get_contained()
    1628              # "loop_list" is, for example: [a, b, c]
    1629              # ie. each element is an object of class "loop".
    1630              # If one element is done, then all other elements are effectively done, the way we've structured the lists to
    1631              # be (they all have same 'N' value).
    1632
    1633              while not loop.is_done() and not loop_info_frame.cancel_asap:
    1634                  for loop_element in loop_list:
    1635                      if loop_element.get_contained().parameters.variable != "<repeat>":
    1636                          self.stim_frame.set_loopable_variable(loop_element.get_contained().parameters.variable,loop_element.get_contained().get_current())
    1637                  if depth < max_depth:
    1638                      process_loops(depth+1)
    1639                  elif depth == max_depth: # deepest level -- do the trial
    1640                      if need_rest_period:
    1641                          self.progress.labelText = "Resting"
    1642                          self.sleep_with_progress(loop.parameters.rest_duration_sec)
    1643                      self.do_single_trial()
    1644                      need_rest_period = 1
    1645                  else:
    1646                      raise RuntimeError("Called with max_depth==-1:")
    1647                  for loop_element in loop_list:
    1648                      loop_element.get_contained().advance()
    1649              for loop_element in loop_list:
    1650                  loop_element.get_contained().reset()
    1651              if depth == 0: # destroy LoopInfoFrame
    1652                  top.destroy()
    1653
    1654          ############################################################
    1655          process_loops(0) # start recursion on top level
    1656
    1657 -    def do_single_trial(self):
    1658          # Get filename to save parameters
    1659          if not self.autosave.get():
    1660              file_stream = None # not saving file
    1661          else:
    1662              duration_sec = self.stim_frame.get_duration_sec()
    1663              (year,month,day,hour24,min,sec) = time.localtime(time.time()+duration_sec)[:6]
    1664              trial_time_str = "%04d%02d%02d_%02d%02d%02d"%(year,month,day,hour24,min,sec)
    1665              if self.param_file_type_tk_var.get() == "Python format":
    1666                  # Figure out filename to save parameters in
    1667                  filename = self.autosave_basename.get() + trial_time_str + "_params.py"
    1668                  fullpath_filename = os.path.join( self.autosave_dir.get(), filename)
    1669                  file_stream = open(fullpath_filename,"w")
    1670              elif self.param_file_type_tk_var.get() == "Matlab format":
    1671                  # Figure out filename to save results in
    1672                  filename = self.autosave_basename.get() + trial_time_str + "_params.m"
    1673                  fullpath_filename = os.path.join( self.autosave_dir.get(), filename)
    1674                  file_stream = open(fullpath_filename,"w")
    1675              else:
    1676                  raise ValueError('Unknown file format: "%s"'%(self.param_file_type_tk_var.get(),))
    1677
    1678          # this class is broken into parts so it can be subclassed more easily
    1679          self.do_single_trial_pre(file_stream)
    1680          self.do_single_trial_work()
    1681          self.do_single_trial_post(file_stream)
    1682
    1683          # Close parameter save file
    1684          if self.autosave.get():
    1685              file_stream.close()
    1686
    1687 -    def do_single_trial_pre(self, file_stream):
    1688          # Ensure that we have the most up-to-date values
    1689          self.stim_frame.send_values() # copy values from Tkinter to self.stim_frame.meta_params and send to server
    1690
    1691          # if file_stream is None, open default file
    1692          if self.autosave.get():
    1693              duration_sec = self.stim_frame.get_duration_sec()
    1694              (year,month,day,hour24,min,sec) = time.localtime(time.time()+duration_sec)[:6]
    1695              if self.param_file_type_tk_var.get() == "Python format":
    1696                  file_stream.write("stim_type = '%s'\n"%self.stim_frame.get_shortname())
    1697                  file_stream.write("finished_time = %04d%02d%02d%02d%02d%02d\n"%(year,month,day,hour24,min,sec))
    1698                  parameter_list = self.stim_frame.get_parameters_as_py_strings()
    1699                  for parameter_name, parameter_value in parameter_list:
    1700                      file_stream.write("%s = %s\n"%(parameter_name, parameter_value))
    1701              elif self.param_file_type_tk_var.get() == "Matlab format":
    1702                  file_stream.write("stim_type = '%s';\n"%self.stim_frame.get_shortname())
    1703                  file_stream.write("finished_time = %04d%02d%02d%02d%02d%02d;\n"%(year,month,day,hour24,min,sec))
    1704                  parameter_list = self.stim_frame.get_parameters_as_m_strings()
    1705                  for parameter_name, parameter_value in parameter_list:
    1706                      file_stream.write("%s = %s;\n"%(parameter_name, parameter_value))
    1707              else:
    1708                  raise RuntimeError("Unknown parameter file type") # Should never get here
    1709
    1710 -    def do_single_trial_work(self):
    1711          # make wait cursor
    1712          root = self.winfo_toplevel()
    1713          self.old_cursor = root["cursor"]
    1714          root["cursor"] = "watch"
    1715          root.update()
    1716          try:
    1717
    1718              self.progress.labelText = "Doing trial..."
    1719              self.progress.updateProgress(0)
    1720
    1721              duration_sec = self.stim_frame.get_duration_sec()
    1722              if duration_sec > 0:
    1723                  if self.override_t_abs_on.get():
    1724                      new_t_abs_str = self.override_t_abs_sec.get()
    1725                      self.ephys_server.set_override_t_abs_sec( new_t_abs_str )
    1726
    1727                  self.stim_frame.go() # start server going, but this return control immediately
    1728                  self.sleep_with_progress(duration_sec)
    1729                  while self.ephys_server.is_in_go_loop(): # make sure go loop is really done
    1730                      time.sleep(0.1) # wait 100 msec for end of go loop and try again
    1731
    1732                  if self.notify_on_dropped_frames.get():
    1733                      if self.ephys_server.were_frames_dropped_in_last_go_loop():
    1734                          tkMessageBox.showwarning("Dropped frame(s)",
    1735                                                   "During the last trial, at least 1 frame was dropped.",
    1736                                                    parent=self)
    1737              else:
    1738                  if self.stim_frame.send_values():
    1739                      self.ephys_server.run_demoscript()
    1740                      self.stim_frame.quit_server()
    1741          finally:
    1742              root["cursor"] = self.old_cursor
    1743              root.update()
    1744
    1745              # restore status bar
    1746              self.progress.labelText = "Ready"
    1747              self.progress.updateProgress(0)
    1748
    1749 -    def do_single_trial_post(self, file_stream):
    1750          # if file_stream is None, open default file
    1751          if self.autosave.get():
    1752              frames_dropped = self.ephys_server.were_frames_dropped_in_last_go_loop()
    1753              go_loop_start_time = self.ephys_server.get_last_go_loop_start_time_absolute_sec()
    1754              if self.param_file_type_tk_var.get() == "Python format":
    1755                  file_stream.write("frames_dropped = %s # boolean\n"%str(frames_dropped))
    1756                  file_stream.write("go_loop_start_time_abs_sec = %s\n"%repr(go_loop_start_time))
    1757              elif self.param_file_type_tk_var.get() == "Matlab format":
    1758                  file_stream.write("frames_dropped = %s; %% boolean\n"%str(frames_dropped))
    1759                  file_stream.write("go_loop_start_time_abs_sec = %s;\n"%repr(go_loop_start_time))
    1760              else:
    1761                  raise RuntimeError("Unknown parameter file type") # Should never get here
    1762
    1763 -    def sleep_with_progress(self, duration_sec):
    1764          if duration_sec == 0.0:
    1765              return # don't do anything
    1766          start_time = time.time()
    1767          stop_time = start_time + duration_sec
    1768          percent_done = 0
    1769          while percent_done < 100:
    1770              if sys.platform != 'darwin': # Mac OS X Tk bug... sigh...
    1771                  self.progress.updateProgress(percent_done)
    1772              time.sleep(0.01) # wait 10 msec
    1773              percent_done = (time.time() - start_time)/duration_sec*100
    1774
    1775 -    def quit(self):
    1776          if self.quit_server_too.get():
    1777              self.ephys_server.set_quit_status(1)
    1778          # call parent class
    1779          Tkinter.Frame.quit(self)
    1780
    1781 -    def destroy(self):
    1782          try:
    1783              if self.quit_server_too.get():
    1784                  self.ephys_server.set_quit_status(1)
    1785          except:
    1786              pass
    1787          Tkinter.Frame.destroy(self)
    1788
    1789 -class BarButton(Tkinter.Menubutton):
    1790      # Taken from Guido van Rossum's Tkinter svkill demo
    1791 -        def __init__(self, master=None, **cnf):
    1792              Tkinter.Menubutton.__init__(self, master, **cnf)
    1793              self.pack(side=Tkinter.LEFT)
    1794              self.menu = Tkinter.Menu(self, name='menu', tearoff=0)
    1795              self['menu'] = self.menu
    1796
    1797  if __name__ == '__main__':
    1798      hostname = os.getenv("ephys_server_hostname","")
    1799      port = int(os.getenv("ephys_server_port","7766"))
    1800      result = get_server(hostname=hostname,port=port)
    1801      if result:
    1802          hostname,port = result
    1803          app_window = AppWindow(client_list=client_list,
    1804                                 server_hostname=hostname,
    1805                                 server_port=port)
    1806
    1807          app_window.winfo_toplevel().wm_iconbitmap()
    1808          app_window.pack(expand=1,fill=Tkinter.BOTH)
    1809          app_window.mainloop()
    1810