Source code for convis.retina_virtualretina

"""

This module is a compatibility layer between the Virtual Retina configurations and behaviour and the python implementation.

"""
import xml.etree.ElementTree as ET
import collections
import copy
from future.utils import iteritems as _iteritems

def dict_recursive_update(d, u):
    for k, v in _iteritems(u):
        if isinstance(v, collections.Mapping):
            r = dict_recursive_update(d.get(k, {}), v)
            d[k] = r
        else:
            d[k] = u[k]
    return d

# This dict contains tag and attribute names used in virtual retina configuration files
valid_retina_tags = {
    'retina': ['temporal-step__sec','input-luminosity-range','pixels-per-degree'],
    'basic-microsaccade-generator': ['pixels-per-degree',
                                'temporal-step__sec',
                                'angular-noise__pi-radians',
                                'period-mean__sec',
                                'period-stdev__sec',
                                'amplitude-mean__deg',
                                'amplitude-stdev__deg',
                                'saccade-duration-mean__sec',
                                'saccade-duration-stdev__sec'],
    'outer-plexiform-layer': [],
    'linear-version': ['center-sigma__deg',
                                'surround-sigma__deg',
                                'center-tau__sec',
                                'center-n__uint',
                                'surround-tau__sec',
                                'opl-amplification',
                                'opl-relative-weight',
                                'leaky-heat-equation'],
    'undershoot': ['relative-weight','tau__sec'],
    'contrast-gain-control': ['opl-amplification__Hz',
                                'bipolar-inert-leaks__Hz',
                                'adaptation-sigma__deg',
                                'adaptation-tau__sec',
                                'adaptation-feedback-amplification__Hz'],
    'ganglion-layer': ['sign',
                                'transient-tau__sec',
                                'transient-relative-weight',
                                'bipolar-linear-threshold',
                                'value-at-linear-threshold__Hz',
                                'bipolar-amplification__Hz',
                                'sigma-pool__deg'],
    'spiking-channel': ['g-leak__Hz',
                                'sigma-V',
                                'refr-mean__sec',
                                'refr-stdev__sec',
                                'random-init'],
    'square-array': ['size-x__deg', 'size-y__deg', 'uniform-density__inv-deg'],
    'circular-array': ['fovea-density__inv-deg','diameter__deg']
}

#only deep copy from this!
_default_config = {
            'basic-microsaccade-generator' :{
                'enabled': False,
                'pixels-per-degree':200,
                'temporal-step__sec':0.005,
                'angular-noise__pi-radians':0.3,
                'period-mean__sec':0.2,
                'period-stdev__sec':0,
                'amplitude-mean__deg':0.5,
                'amplitude-stdev__deg':0.1,
                'saccade-duration-mean__sec':0.025,
                'saccade-duration-stdev__sec':0.005,
            },
            'retina': {
                'temporal-step__sec':0.01,
                'input-luminosity-range':255,
                'pixels-per-degree':5.0
            },
            'log-polar-scheme' : {
                'enabled': False,
                'fovea-radius__deg': 1.0,
                'scaling-factor-outside-fovea__inv-deg': 1.0
            },
            'outer-plexiform-layers': [
                {
                    'linear-version': {
                        'center-sigma__deg': 0.05,
                        'surround-sigma__deg': 0.15,
                        'center-tau__sec': 0.01,
                        'center-n__uint': 0,
                        'surround-tau__sec': 0.004,
                        'opl-amplification': 10,
                        'opl-relative-weight': 1,
                        'leaky-heat-equation': 1,
                        'undershoot': {
                            'enabled': True,
                            'relative-weight': 0.8,
                            'tau__sec': 0.1
                        }                    
                    }
                }
            ],
            'contrast-gain-control': {
                'opl-amplification__Hz': 50, # for linear OPL: ampOPL = relative_ampOPL / fatherRetina->input_luminosity_range ;
                'bipolar-inert-leaks__Hz': 50,
                'adaptation-sigma__deg': 0.2,
                'adaptation-tau__sec': 0.005,
                'adaptation-feedback-amplification__Hz': 0
            },
            'ganglion-layers': [
                {
                    'name': 'Parvocellular On',
                    'enabled': True,
                    'sign': 1,
                    'transient-tau__sec':0.02,
                    'transient-relative-weight':0.7,
                    'bipolar-linear-threshold':0,
                    'value-at-linear-threshold__Hz':37,
                    'bipolar-amplification__Hz':100,
                    'sigma-pool__deg': 0.0,
                    'spiking-channel': {
                        'g-leak__Hz': 50,
                        'sigma-V': 0.1,
                        'refr-mean__sec': 0.003,
                        'refr-stdev__sec': 0,
                        'random-init': 0,
                        'square-array': {
                            'size-x__deg': 4,
                            'size-y__deg': 4,
                            'uniform-density__inv-deg': 20
                        }
                    }
                },
                {
                    'name': 'Parvocellular Off',
                    'enabled': True,
                    'sign': -1,
                    'transient-tau__sec':0.02,
                    'transient-relative-weight':0.7,
                    'bipolar-linear-threshold':0,
                    'value-at-linear-threshold__Hz':37,
                    'bipolar-amplification__Hz':100,
                    'sigma-pool__deg': 0.0,
                    'spiking-channel': {
                        'g-leak__Hz': 50,
                        'sigma-V': 0.1,
                        'refr-mean__sec': 0.003,
                        'refr-stdev__sec': 0,
                        'random-init': 0,
                        'square-array': {
                            'size-x__deg': 4,
                            'size-y__deg': 4,
                            'uniform-density__inv-deg': 20
                        }
                    }
                },
                {
                    'name': 'Magnocellular On',
                    'enabled': False,
                    'sign': 1,
                    'transient-tau__sec':0.03,
                    'transient-relative-weight':1.0,
                    'bipolar-linear-threshold':0,
                    'value-at-linear-threshold__Hz':80,
                    'bipolar-amplification__Hz':400,
                    'sigma-pool__deg': 0.1,
                    'spiking-channel': {
                        'g-leak__Hz': 50,
                        'sigma-V': 0,
                        'refr-mean__sec': 0.003,
                        'refr-stdev__sec': 0,
                        'random-init': 1,
                        'circular-array': {
                            'fovea-density__inv-deg': 15.0
                        }
                    }
                },
                {
                    'name': 'Magnocellular Off',
                    'enabled': False,
                    'sign': -1,
                    'transient-tau__sec':0.03,
                    'transient-relative-weight':1.0,
                    'bipolar-linear-threshold':0,
                    'value-at-linear-threshold__Hz':80,
                    'bipolar-amplification__Hz':400,
                    'sigma-pool__deg': 0.1,
                    'spiking-channel': {
                        'g-leak__Hz': 50,
                        'sigma-V': 0,
                        'refr-mean__sec': 0.003,
                        'refr-stdev__sec': 0,
                        'random-init': 1,
                        'circular-array': {
                            'fovea-density__inv-deg': 15.0
                        }
                    }
                }
            ]
        }
_config_info = {
            'basic-microsaccade-generator' :{
                'enabled': {
                        'default':False, 
                        'range':[False,True],
                        'doc': "The microsaccade generator is not implemented in convis."
                        },
                'pixels-per-degree':{ 'default': 200 },
                'temporal-step__sec':{ 'default': 0.005},
                'angular-noise__pi-radians':{ 'default': 0.3},
                'period-mean__sec':{ 'default': 0.2},
                'period-stdev__sec':{ 'default': 0},
                'amplitude-mean__deg':{ 'default': 0.5},
                'amplitude-stdev__deg':{ 'default': 0.1},
                'saccade-duration-mean__sec':{ 'default': 0.025},
                'saccade-duration-stdev__sec':{ 'default': 0.005},
            },
            'retina': {
                'temporal-step__sec':{ 'default': 0.01 },
                'input-luminosity-range':{ 'default': 255 },
                'pixels-per-degree':{ 'default': 5.0 }
            },
            'log-polar-scheme' : {
                'enabled': { 'default': False},
                'fovea-radius__deg': { 'default': 1.0},
                'scaling-factor-outside-fovea__inv-deg': { 'default': 1.0}
            },
            'outer-plexiform-layers': [
                {
                    'linear-version': {
                        'center-sigma__deg': { 
                            'default': 0.05
                            },
                        'surround-sigma__deg': { 
                            'default': 0.15
                            },
                        'center-tau__sec': {
                            'default': 0.01
                            },
                        'center-n__uint': {
                            'default': 0
                            },
                        'surround-tau__sec': {
                            'default': 0.004
                            },
                        'opl-amplification': {
                            'default': 10
                            },
                        'opl-relative-weight': {
                            'default': 1
                            },
                        'leaky-heat-equation': {
                            'default': 1
                            },
                        'undershoot': {
                            'enabled': { 'default': True},
                            'relative-weight': { 'default': 0.8},
                            'tau__sec': { 'default': 0.1}
                        }                    
                    }
                }
            ],
            'contrast-gain-control': {
                'opl-amplification__Hz': { 'default': 50}, # for linear OPL: ampOPL = relative_ampOPL / fatherRetina->input_luminosity_range ;
                'bipolar-inert-leaks__Hz': { 'default': 50},
                'adaptation-sigma__deg': { 'default': 0.2},
                'adaptation-tau__sec': { 'default': 0.005},
                'adaptation-feedback-amplification__Hz': { 'default': 0}
            },
            'ganglion-layers': [
                {
                    'name': { 
                        'default': 'Cell Layer' },
                    'enabled': { 
                        'default': True},
                    'sign': { 
                        'default': {'On': 1, 'Off': -1}, 
                        'values': [-1,1] },
                    'transient-tau__sec':{ 
                        'default': {'Parvocellular': 0.02, 'Magnocellular': 0.03}, 
                        'range': ['log',0.0001,1.0]},
                    'transient-relative-weight':{ 
                        'default': {'Parvocellular': 0.7, 'Magnocellular': 1.0},
                        'range':[0.0,1.0]},
                    'bipolar-linear-threshold':{ 
                        'default': 0, 
                        'var_name':'i_0_g',
                        'range': [-1.0,1.0]},
                    'value-at-linear-threshold__Hz':{ 
                        'default': {'Parvocellular': 37.0, 'Magnocellular': 80.0}
                        },
                    'bipolar-amplification__Hz':{ 
                        'default': {'Parvocellular': 0, 'Magnocellular':400},
                        'range': [0,600]
                        },
                    'sigma-pool__deg': {
                        'default': {'Parvocellular': 0.0, 'Magnocellular': 0.1}
                        },
                    'spiking-channel': {
                        'g-leak__Hz': { 
                            'default': 50
                            },
                        'sigma-V': { 
                            'default': 0.1
                            },
                        'refr-mean__sec': { 
                            'default': 0.003
                            },
                        'refr-stdev__sec': { 
                            'default': 0
                            },
                        'random-init': { 
                            'default': 1
                            },
                        'square-array': {
                            'size-x__deg': { 
                                'default': 4
                                },
                            'size-y__deg': { 
                                'default': 4
                                },
                            'uniform-density__inv-deg': { 
                                'default': 20
                                }
                        }
                    }
                }
            ]
        }


[docs]class RetinaConfiguration: """ A configuration object that writes an xml file for VirtualRetina. (When this is altered, silver.glue.RetinaConfiguration has to also be updated by hand) Does not currently care to parse an xml file, but can save/load in json instead. The defaults are equal to `human.parvo.xml`. Values can be changed either directly in the configuration dictionary, or with the `set` helperfunction:: config = silver.glue.RetinaConfiguration() config.retina_config['retina']['input-luminosity-range'] = 200 config.set('basic-microsaccade-generator.enabled') = True config.set('ganglion-layers.*.spiking-channel.sigma-V') = 0.5 # for all layers """ def __init__(self,updates={}): self.default() self.retina_config = dict_recursive_update(self.retina_config,updates) def default(self): """ Generates a default config:: self.retina_config = """ + str(_default_config) self.retina_config = copy.deepcopy(_default_config)
[docs] def get(self,key,default=None): """ Retrieves values from the configuration. conf.set("ganglion-layers.*.spiking-channel.sigma-V",None) # gets the value for all layers conf.set("ganglion-layers.0",{}) # gets the first layer """ if key == 'pixels-per-degree': return self.retina_config.get('retina',{}).get('pixels-per-degree',default) def get(config,key,default): if key == '': return config key = key.split('.') if type(config) == dict: if key[0] == '*': # this will probably fail most of the time because the trees afterward are not identical return [get(config[c],'.'.join(key[1:]),default) for c in config.keys()] if key[0] in config: return get(config[key[0]],'.'.join(key[1:]),default) elif type(config) in (list,tuple): if key[0] == '*': return [get(c,'.'.join(key[1:]),default) for c in config] return get(config[int(key[0])],'.'.join(key[1:]),default) return default return get(self.retina_config,key,default)
[docs] def set(self,key,value,layer_filter=None): """ shortcuts for frequent configuration values Knows where to put: 'pixels-per-degree', 'size__deg' (if x and y are equal), 'uniform-density__inv-deg' all attributes of linear-version all attributes of undershoot Understands dot notation:: conf = silver.glue.RetinaConfiguration() conf.set("ganglion-layers.2.enabled",True) conf.set("ganglion-layers.*.spiking-channel.sigma-V",0.101) # changes the value for all layers But whole sub-tree dicitonaries can be set as well (they replace, not update):: conf.set('contrast-gain-control', {'opl-amplification__Hz': 50, 'bipolar-inert-leaks__Hz': 50, 'adaptation-sigma__deg': 0.2, 'adaptation-tau__sec': 0.005, 'adaptation-feedback-amplification__Hz': 0 }) New dictionary keys are created automatically, new list elements can be created like this:: conf.set("ganglion-layers.=.enabled",True) # copies all values from the last element conf.set("ganglion-layers.=1.enabled",True) # copies all values from list element 1 conf.set("ganglion-layers.+.enabled",True) # creates a new (empty) dictionary which is probably underspecified conf.set("ganglion-layers.+",{ 'name': 'Parvocellular On', 'enabled': True, 'sign': 1, 'transient-tau__sec':0.02, 'transient-relative-weight':0.7, 'bipolar-linear-threshold':0, 'value-at-linear-threshold__Hz':37, 'bipolar-amplification__Hz':100, 'spiking-channel': { 'g-leak__Hz': 50, 'sigma-V': 0.1, 'refr-mean__sec': 0.003, 'refr-stdev__sec': 0, 'random-init': 0, 'square-array': { 'size-x__deg': 4, 'size-y__deg': 4, 'uniform-density__inv-deg': 20 } } }) # ganglion cell layer creates a new dicitonary """ if key == 'pixels-per-degree': self.retina_config['retina']['pixels-per-degree'] = value if 'basic-microsaccade-generator' in self.retina_config and 'pixels-per-degree' in self.retina_config['basic-microsaccade-generator']: self.retina_config['basic-microsaccade-generator']['pixels-per-degree'] = value elif key in valid_retina_tags['linear-version']: self.retina_config['outer-plexiform-layers'][0]['linear-version'][key] = value elif key in valid_retina_tags['undershoot']: self.retina_config['outer-plexiform-layers'][0]['linear-version']['undershoot'][key] = value elif key == 'size__deg': for l in self.retina_config.get('ganglion-layers',[]): if layer_filter is not None and not layer_filter in l['name']: continue l['spiking-channel'] = l.get('spiking-channel',{}) l['spiking-channel']['square-array'] = l['spiking-channel'].get('square-array',{}) l['spiking-channel']['square-array']['enabled'] = True l['spiking-channel']['square-array']['size-x__deg'] = value l['spiking-channel']['square-array']['size-y__deg'] = value elif key == 'uniform-density__inv-deg': for l in self.retina_config.get('ganglion-layers',[]): if layer_filter is not None and not layer_filter in l['name']: continue l['spiking-channel'] = l.get('spiking-channel',{}) l['spiking-channel']['square-array'] = l['spiking-channel'].get('square-array',{}) l['spiking-channel']['square-array']['enabled'] = True l['spiking-channel']['square-array']['uniform-density__inv-deg'] = value elif key == 'enabled': for l in self.retina_config.get('ganglion-layers',[]): if layer_filter is not None and not layer_filter in l['name']: continue l['enabled'] = value else: # Shortcut dot notation def put(d, keys, item): if "." in keys: key, rest = keys.split(".", 1) if type(d) is list: if key == "+": d.append({}) put(d[-1], rest, item) elif key == "=": d.append(d[-1]) put(d[-1], rest, item) elif key.startswith("="): d.append(d[int(key[1:])]) # use the referenced element put(d[-1], rest, item) elif key == "*": for i in range(len(d)): put(d[i], rest, item) else: while int(key) >= len(d): d.append({}) put(d[int(key)], rest, item) else: if key == "*": for k in d.keys(): put(d[k], rest, item) else: if key not in d: d[key] = {} put(d[key], rest, item) else: if type(d) is list: if key == "+": d.append({}) d[-1] = item else: while int(key) >= len(d): d.append({}) d[int(key)] = item else: d[keys] = item put(self.retina_config,key,value)
[docs] def read_json(self,filename): """ Reads a full retina config json file. """ self.retina_config = json.load(filename)
def _read_xml(self,filename): def get_attributes(tag_name,parent): attribs = {} for k in parent.attrib.keys(): if k in valid_retina_tags[tag_name]: attribs[k] = parent.attrib[k] return attribs def extend_dict(tag_name,d,config): # if possible, extend the dictionary with the sub key of this element try: d[tag_name] = get_attributes(tag_name,config.find(tag_name)) return True except: pass return False tree = ET.parse(filename) root = tree.getroot() new_config = {} retina = root.find('retina') new_config['retina'] = get_attributes('retina',retina) # these nodes have no children: extend_dict('contrast-gain-control',new_config,retina) extend_dict('basic-microsaccade-generator',new_config,retina) extend_dict('log-polar-scheme',new_config,retina) # these might have children: new_config['outer-plexiform-layers'] = [] new_config['ganglion-layers'] = [] for opl in retina.findall('outer-plexiform-layer'): opl_config = get_attributes('outer-plexiform-layer',opl) linear_version_config = get_attributes('linear-version',opl.find('linear-version')) extend_dict('undershoot',linear_version_config,opl.find('linear-version')) opl_config['linear-version'] = linear_version_config new_config['outer-plexiform-layers'].append(opl_config) for gl in retina.findall('ganglion-layer'): gl_config = get_attributes('ganglion-layer',gl) if extend_dict('spiking-channel',gl_config,gl): extend_dict('square-array',gl_config['spiking-channel'],gl.find('spiking-channel')) extend_dict('circular-array',gl_config['spiking-channel'],gl.find('spiking-channel')) try: units = gl.find('spiking-channel').find('all-units') gl_config['spiking-channel']['units'] = [] if units is not None: for u in units.findall('unit'): # we assume that all units have complete information or we fail gl_config['spiking-channel']['units'].append({ 'x': u.attrib.get('x-offset__deg'), 'y': u.attrib.get('y-offset__deg'), 'id': u.attrib.get('mvaspike-id') }) except: raise new_config['ganglion-layers'].append(gl_config) return new_config def read_xml(self,filename): self.retina_config = self._read_xml(filename) def update_with_xml(self,filename): self.retina_config = dict_recursive_update(self.retina_config,self._read_xml(filename)) def write_json(self,filename): json.dump(self.retina_config,filename)
[docs] def write_json(self,filename): """ Writes a retina config json file. """ json.dump(self.retina_config,filename)
[docs] def write_xml(self,filename): """ Writes a full retina config xml file. Attributes that are not understood by the original Virtual Retina are removed. """ def add_element(tag_name,parent,config,config_is_parent_config=True): if parent is None: return if config_is_parent_config: config = config.get(tag_name,{'enabled':False}) if 'enabled' not in config or config['enabled']: e = ET.SubElement(parent, tag_name) for k in config.keys(): if k in valid_retina_tags[tag_name]: e.set(k,str(config[k])) return e self.tree = ET.Element('retina-description-file') add_element('basic-microsaccade-generator', self.tree, self.retina_config) retina = add_element('retina', self.tree, self.retina_config) add_element('log-polar-scheme', retina, self.retina_config) for layer_config in self.retina_config.get('outer-plexiform-layers',[]): opl_layer = add_element('outer-plexiform-layer', retina, layer_config,False) lin = add_element('linear-version', opl_layer, layer_config, True) undershoot = add_element('undershoot', lin, layer_config.get('linear-version',{})) add_element('contrast-gain-control', retina, self.retina_config) layer_start = 0 for layer_config in self.retina_config.get('ganglion-layers',[]): ganglion_layer = add_element('ganglion-layer', retina, layer_config,False) spiking_channel = add_element('spiking-channel', ganglion_layer, layer_config) if spiking_channel is not None and 'units' in layer_config.get('spiking-channel',{}): all_units = ET.SubElement(spiking_channel, 'all-units') for i,u in enumerate(layer_config.get('spiking-channel',{}).get('units',[])): unit = ET.SubElement(all_units, 'unit') unit.set('x-offset__deg',str(u.get('x',0.0))) unit.set('y-offset__deg',str(u.get('y',0.0))) unit.set('mvaspike-id',str(u.get('id',i+layer_start))) layer_start += len(layer_config.get('units',[])) square_array = add_element('square-array', spiking_channel, layer_config.get('spiking-channel',{'enabled':False})) circular_array = add_element('circular-array', spiking_channel, layer_config.get('spiking-channel',{'enabled':False})) with open(filename,'w') as f: f.write(ET.tostring(self.tree))
def copy(self): import copy return RetinaConfiguration(copy.copy(self.retina_config))
def default_config(): return RetinaConfiguration() def random_config(): return RetinaConfiguration()
[docs]def deriche_filter_density_map(retina, sigma0 = 1.0, Nx = None, Ny = None): """ Returns a map of how strongly a point is to be blurred. Relevant config options of retina:: 'log-polar-scheme' : { 'enabled': True, 'fovea-radius__deg': 1.0, 'scaling-factor-outside-fovea__inv-deg': 1.0 } or for a circular (constant) scheme:: 'log-polar-scheme' : { 'enabled': False, 'fovea-radius__deg': 1.0, 'scaling-factor-outside-fovea__inv-deg': 1.0 } The output should be used with `retina_base.deriche_coefficients` to generate the coefficient maps for a Deriche filter. """ import numpy as np Ny = Nx if Ny is None else Ny x, y = np.meshgrid(np.arange(Nx),np.arange(Ny)) midx = midy = Nx/2.0 ratiopix = retina.degree_to_pixel(1.0) r = np.sqrt((x-midx)**2 + (y-midy)**2) + 0.001 density = np.ones_like(r) log_polar_config = retina.config.retina_config.get('log-polar-scheme',{}) if log_polar_config.get('enabled',False): log_polar_K = log_polar_config.get('scaling-factor-outside-fovea__inv-deg', 1.0) log_polar_R0 = log_polar_config.get('fovea-radius__deg', 1.0) if log_polar_K is None or log_polar_K < 0.0: log_polar_K = 1.0/log_polar_R0 density = r density[r>log_polar_R0] = log_polar_R0 + log(1+log_polar_K*(density[r>log_polar_R0]-log_polar_R0))/log_polar_K return density/(sigma0*ratiopix)