[visionegg] Re: Stimuli updates not rendering

Hi,

thanks for the input. Unfortunately I made no progress, so I'll post the complete code.

First of all I'd like to explain the idea of the code a bit, so it won't become too confusing. This code is intended to be a support for scientists in our group who have little to no computer science background (i.e. are not able to use VisionEgg directly). They will be able to set up experiments by defining them in relatively simple and sparse config files. Consequently, ease of use will be a major factor, which also means that some of the decisions
will be made for them.

The current status is, that this is very work in progress code - I'd like to rough out the general idea before making it robust (no warning/error conditions, not every kind of stimuli supported, maybe some bugs hidden in there - the supplied config file works though if you'd like to give it a test run; refactoring and thorough testing will be a major point later on). Below you'll find the code with some comments stripped out as they are mainly concerned with stuff that is on the todo-list - so if there are any questions please feel free to ask. Please disregard unused variables at this point they are once
again serving as a reminder for me on the todo-list. =)

#------------------------------experiment.ini (config)---------------------------------------

experiment_duration = 5
background_color = 0.0, 0.0, 0.0, 0.0
stimuli = stim1, stim2, stim3
stimuli_position_unit = percent
stimuli_horizontal_positions = 20, 50, 80
stimuli_vertical_positions = 20, 50, 80
stimuli_frequencies = 6, 12, 30

[ stim1 ]
class = Circle
radius = 25.0
color = 255, 0, 0

[ stim2 ]
class = Rectangle
size = 50, 50
color = 255, 0, 255, 1
orientation = 0

[ stim3 ]
class = Arrow
size = 64.0, 16.0
color = 1, 1, 1
orientation = 45

# --------------------------- code ----------------------------------------------

import VisionEgg
VisionEgg.start_default_logging() VisionEgg.watch_exceptions()
from VisionEgg.Core import *
from VisionEgg.FlowControl import Presentation from VisionEgg.MoreStimuli import *
from configobj import ConfigObj

import sys import getopt
class SsvepStimulator:

   def __init__(self):
       self.verbosity = 1
       # a list of stimuli as later defined by the user
       # the added s is deliberate as to easier avoid potential
       # namespace classes
       self.stimulis = []
       # direct access to the configuration
       self.config = ConfigObj()
       # initializing a screen for later use
       self.screen = get_default_screen()
       # initialize lists for vertical and horizontal stimuli
       # positioning coordinates
       self.stim_vert_pos = []
       self.stim_hori_pos = []
       # initialize a list which will later hold frequency values
       # converted to relative intervals in seconds
       self.stimulis_freq_sec = []
       self.viewport = Viewport(screen=self.screen)

   def configureStimuli(self, stimulus):
       """
       The 'configureStimuli' function automagically configures
       Stimuli objects according to user defined settings.
       Some settings are not available to the user and are set by
       DEFAULT. Defaults may overlap between this function and
       VisionEgg. Nevertheless it is important to set them as to
       avoid freak errors if at any time VisionEggs defaults change.
       VisionEggs depreceated settings were disregarded.

       User set options from the experiment configuratiion arrive in
       string format. So necessary these values were converted to
       their respective types.

       Current supported stimuli:
           Circle
           Arrow
           Rectangle
       """
       if self.config[stimulus]['class'] == 'Circle':
           stimulus = FilledCircle(
               # DEFAULT: standard anchor of 'center' which is
               # not redefinable by the user
               anchor = 'center',
               # DEFAULT: draw the stimulus
               on = True,
               # taking the radius from the config and
               # converting it to float
               radius =\
               float(self.config[stimulus]['radius']),
               # taking the color values from the config and
               # converting it to float due to the mapping it
               # works with RGB and RGBA
               color = map(float,\
                   self.config[stimulus]['color']),
               # just take the position as it has already
               # been computed
               # we are dealing with a tuple here for which
               # we get the already computed positions by
               # identifying them according to their index
               # within the stimulis
               position = (self.stim_hori_pos[self.config[\
                   'stimuli'].index(stimulus)], \
                   self.stim_vert_pos[self.config[\
                   'stimuli'].index(stimulus)]),
               num_triangles = 500
               )
           return stimulus
       elif self.config[stimulus]['class'] == 'Arrow':
           stimulus = Arrow(
               # DEFAULT: standard anchor of 'center' which is
               # not redefinable by the user
               anchor = 'center',
               # DEFAULT: anti aliasing is on and not
               # changeable by the user
               anti_aliasing = True,
               # DEFAULT: draw the stimulus
               on = True,
               # take the orientation as defined by the user
               # converting it to float on the go
               orientation = float(\
                   self.config[stimulus]['orientation']),
               # taking the color values from the config and
               # converting it to float due to the mapping it
               # works with RGB and RGBA
               color = map(float,\
                   self.config[stimulus]['color']),
               # just take the position as it has already
               # been computed
               # we are dealing with a tuple here for which
               # we get the already computed positions by
               # identifying them according to their index
               # within the stimulis
               position = (self.stim_hori_pos[self.config[\
                   'stimuli'].index(stimulus)], \
                   self.stim_vert_pos[self.config[\
                   'stimuli'].index(stimulus)]),
               # take the size as specified by the user
               size = map(float, self.config[stimulus]['size'])
               )
           return stimulus
       elif self.config[stimulus]['class'] == 'Rectangle':
           stimulus = Target2D(
               # DEFAULT: standard anchor of 'center' which is
               # not redefinable by the user
               anchor = 'center',
               # DEFAULT: anti aliasing is on and not
               # changeable by the user
               anti_aliasing = True,
               # DEFAULT: draw the stimulus
               on = True,
               # take the orientation as defined by the user
               # converting it to float on the go
               orientation = float(\
                   self.config[stimulus]['orientation']),
               # taking the color values from the config and
               # converting it to float due to the mapping it
               # works with RGB and RGBA
               color = map(float,\
                   self.config[stimulus]['color']),
               # just take the position as it has already
               # been computed
               position = (self.stim_hori_pos[self.config[\
                   'stimuli'].index(stimulus)], \
                   self.stim_vert_pos[self.config[\
                   'stimuli'].index(stimulus)]),
               # take the size as specified by the user
               size = map(float, self.config[stimulus]['size'])
               )
           return stimulus
       else:
           pass    # TODO error - unknown stimulus
def frequencies_to_sec(self, stimuli_frequencies):
       monitor_refresh = VisionEgg.config.VISIONEGG_MONITOR_REFRESH_HZ
       if filter((lambda x: x > monitor_refresh), stimuli_frequencies):
           sys.exit()
       else:
           # check for every specified frequency
           for frequency in stimuli_frequencies:
               # if we are theoretically able to render it
               if (monitor_refresh % frequency) == 0:
                   # we convert HZ to second intervals
                   self.stimulis_freq_sec.append(\
                       1 / frequency)
               else:
                   pass

   def frequency_logic(self, t):
       # I had some logic in here before but stripped it out
       # for now as it wasn't refined at all - for the problem
       # it should just suffice to render nothing which it
       # unfortunately doesn't do
       self.stimulis[\
           self.stimulis_freq_sec.index(\
           interval)].__setattr__('on', False)
#                print self.stimulis[\
#                                        self.stimulis_freq_sec.index(\
#                                        interval)].__getattribute__('on')
       # I am returning the list of stimuli as I am of the impression
       # that the controller needs this
       return self.stimulis
def executeStimulator(self, config_file, log_file, verbosity_level):
       """
       This function holds the core logic of the  SSVEP stimulator.
       Namely handling configuration options as specified by the user
       in the experiment config. Creating the stimuli and rendering
       them on screen.
""" # connecting to the specified config_file
       self.config = ConfigObj(config_file)
# compute positioning of stimuli according to a user specified
       # list of either percentul or pixel values
       # NOTE: percentual values are a service to the user, because
       # of a pixels nature of non-divisionability, rounding errors
       # may occur
       if self.config['stimuli_position_unit'] == 'percent':
           # compute vertical positions of stimuli according to
           # their respective percentual value - the position
           # will have to be in pixel values
           self.stim_vert_pos = map(round, map(
               (lambda x: float(self.screen.size[1]) / 100.0 \
               * x), map(float, \
               self.config['stimuli_vertical_positions'])))
           # do the same for horizontal stimuli positions
           self.stim_hori_pos = map(round, map(
               (lambda x: float(self.screen.size[0]) / 100.0 \
               * x), map(float, \
               self.config['stimuli_horizontal_positions'])))
       elif self.config['stimuli_position_unit'] == 'pixel':
           # if the user decided to give us raw pixel values we
           # are happy and simply copy them
           self.stim_vert_pos = map(float, \
               self.config['stimuli_vertical_positions'])
           self.stim_hori_pos = map(float, \
               self.config['stimuli_horizontal_positions'])
       else:
           pass
# use the list of stimuli definitions from the config file
       self.stimulis = self.config['stimuli']
       # and create actual Stimulus objects according their individual
       # specification
       self.stimulis = map(self.configureStimuli, self.stimulis)
# using the background color as defined in the config
       self.screen.parameters.bgcolor = map(float,\
               self.config['background_color'])
self.frequencies_to_sec(\
               map(float, self.config['stimuli_frequencies']))

       self.viewport = Viewport(screen=self.screen,\
           stimuli=self.stimulis)
       frequency_controller = FunctionController(\
               during_go_func = self.frequency_logic,\
               eval_frequency = Controller.EVERY_FRAME,\
               temporal_variables = \
               Controller.TIME_SEC_SINCE_GO)
       presentation = Presentation( go_duration=(\
               float(self.config['experiment_duration']),\
               'seconds'),\
               viewports=[self.viewport])
       presentation.add_controller(self.viewport, 'stimuli',\
               frequency_controller)
       presentation.go()

def main(argv):
   try:
       # grab command line arguments
       opts, args = getopt.getopt(argv, "hc:l:v:", \
               ["help", "config=", "log=", "verbosity="])
   except getopt.GetoptError:
       # we encountered a flag/options we could not handle, so we
       # explain the proper usage of short/long flags for this program
usage() # TODO code the usage description # and exit gracefully
       sys.exit(2)
   log_file = "testlog"
   # DEFAULT: log both warning and error messages - more details can be
   # found in the class init of SSVEP-Stimulator
   verbosity_level = 1
   # parse through given command line parameters and act accordingly
   for opt, arg in opts:
       if opt in ("-h", "--help"):
           # if the user request help, show the proper usage
           # of flags for this program
           usage()
           # and exit gracefully
           sys.exit()
       elif opt in ("-c", "--config"):
           # if the users supplies us with a config, take it and
           # use it for the experiment
           config_file = arg
       elif opt in ("-l", "--log"):
           # name the log file as wished by the user
           log_file = arg
       elif opt in ("-v", "--verbosity"):
           verbosity_level = arg
   # pass the configuration along to the stimulator
   # workhorse, after realizing an object of it
   ssvepStim = SsvepStimulator()
   # we need the name of the config file as string so
   # we convert on the fly
   ssvepStim.executeStimulator("".join(config_file), "".join(log_file), \
           verbosity_level)
if __name__ == "__main__":
   # go directly to the main function and let it handle the command line
   # arguments - do not start at index zero as this carries the name of the
   # script and the current working directory by default
   main(sys.argv[1:])

# --------------------------------------------------------------------------------------------------------------------

To see this code in action simply use: python code_file.py -c experiment.ini
The flow tries to be as straight forward as possible; grab config options and set them; grab stimuli definitions and initialize corresponding objects; start the stimulation with Hz rates (currently it should just set the stimulis' 'on'
option to 'False' and not render them - albeit they do get rendered).

Thank you for having a look at the code, if there are any questions please feel free to ask. I hope it is not too confusing as there is a lot of code which is just
concerned with getting values from the experiment config file.

Best regards,
Thorsten Pfister

P.S.: I hope this mail gets threaded right into the list, because I neither get your answers nor a daily digest via email and have to check them via the web interface.
======================================
The Vision Egg mailing list
Archives: http://www.freelists.org/archives/visionegg
Website: http://www.visionegg.org/mailinglist.html

Other related posts: