Package topo :: Package analysis :: Module featureresponses
[hide private]
[frames] | no frames]

Source Code for Module topo.analysis.featureresponses

   1  """ 
   2  FeatureResponses and associated functions and classes. 
   3   
   4  These classes implement map and tuning curve measurement based 
   5  on measuring responses while varying features of an input pattern. 
   6   
   7  $Id: featureresponses.py 11316 2010-07-27 17:52:53Z ceball $ 
   8  """ 
   9  __version__='$Revision: 11316 $' 
  10   
  11   
  12  import copy 
  13   
  14  from math import pi 
  15  from colorsys import hsv_to_rgb 
  16   
  17  import numpy 
  18  from numpy import zeros, empty, object_, size, vectorize, fromfunction 
  19  from numpy.oldnumeric import Float 
  20   
  21  import param 
  22  from param.parameterized import ParameterizedFunction, ParamOverrides 
  23   
  24  import topo 
  25  import topo.base.sheetcoords 
  26  from topo.base.arrayutil import wrap 
  27  from topo.base.cf import CFSheet 
  28  from topo.base.functionfamily import PatternDrivenAnalysis  
  29  from topo.base.sheet import Sheet, activity_type 
  30  from topo.base.sheetview import SheetView 
  31  from topo.command.basic import pattern_present,restore_input_generators, save_input_generators 
  32  from topo.misc.distribution import Distribution 
  33  from topo.misc.util import cross_product, frange 
  34  from topo import pattern 
  35  from topo.pattern.basic import SineGrating, Gaussian, RawRectangle, Disk 
  36  from topo.plotting.plotgroup import plotgroups 
  37  from topo.sheet import GeneratorSheet 
38 39 40 # CB: having a class called DistributionMatrix with an attribute 41 # distribution_matrix to hold the distribution matrix seems silly. 42 # Either rename distribution_matrix or make DistributionMatrix into 43 # a matrix. 44 -class DistributionMatrix(param.Parameterized):
45 """ 46 Maintains a matrix of Distributions (each of which is a dictionary 47 of (feature value: activity) pairs). 48 49 The matrix contains one Distribution for each unit in a 50 rectangular matrix (given by the matrix_shape constructor 51 argument). The contents of each Distribution can be updated for a 52 given bin value all at once by providing a matrix of new values to 53 update(). 54 55 The results can then be accessed as a matrix of weighted averages 56 (which can be used as a preference map) and/or a selectivity 57 map (which measures the peakedness of each distribution). 58 """
59 - def __init__(self,matrix_shape,axis_range=(0.0,1.0), cyclic=False,keep_peak=True):
60 """Initialize the internal data structure: a matrix of Distribution objects.""" 61 self.axis_range=axis_range 62 new_distribution = vectorize(lambda x: Distribution(axis_range,cyclic,keep_peak), 63 doc="Return a Distribution instance for each element of x.") 64 self.distribution_matrix = new_distribution(empty(matrix_shape))
65 66 67
68 - def update(self, new_values, bin):
69 """Add a new matrix of histogram values for a given bin value.""" 70 ### JABHACKALERT! The Distribution class should override +=, 71 ### rather than + as used here, because this operation 72 ### actually modifies the distribution_matrix, but that has 73 ### not yet been done. Alternatively, it could use a different 74 ### function name altogether (e.g. update(x,y)). 75 self.distribution_matrix + fromfunction(vectorize(lambda i,j: {bin:new_values[i,j]}), 76 new_values.shape)
77 78
79 - def weighted_average(self):
80 """Return the weighted average of each Distribution as a matrix.""" 81 82 weighted_average_matrix=zeros(self.distribution_matrix.shape,Float) 83 84 for i in range(len(weighted_average_matrix)): 85 for j in range(len(weighted_average_matrix[i])): 86 weighted_average_matrix[i,j]=self.distribution_matrix[i,j].weighted_average() 87 # weighted_average_matrix[i,j]=self.distribution_matrix[i,j].estimated_maximum() 88 89 90 return weighted_average_matrix
91 92
93 - def max_value_bin(self):
94 """Return the bin with the max value of each Distribution as a matrix.""" 95 96 max_value_bin_matrix=zeros(self.distribution_matrix.shape,Float) 97 98 for i in range(len(max_value_bin_matrix)): 99 for j in range(len(max_value_bin_matrix[i])): 100 max_value_bin_matrix[i,j]=self.distribution_matrix[i,j].max_value_bin() 101 102 return max_value_bin_matrix
103 104 105
106 - def selectivity(self):
107 """Return the selectivity of each Distribution as a matrix.""" 108 109 selectivity_matrix=zeros(self.distribution_matrix.shape,Float) 110 111 for i in range(len(selectivity_matrix)): 112 for j in range(len(selectivity_matrix[i])): 113 selectivity_matrix[i,j]=self.distribution_matrix[i,j].selectivity() 114 115 return selectivity_matrix
116
117 118 119 -class FullMatrix(param.Parameterized):
120 """ 121 Records the output of every unit in a sheet, for every combination of feature values. 122 Useful for collecting data for later analysis while presenting many input patterns. 123 """ 124
125 - def __init__(self,matrix_shape,features):
126 self.matrix_shape = matrix_shape 127 self.features = features 128 self.dimensions = () 129 for f in features: 130 self.dimensions = self.dimensions + (size(f.values),) 131 self.full_matrix = empty(self.dimensions,object_)
132 133
134 - def update(self, new_values, feature_value_permutation):
135 """Add a new matrix of histogram values for a given bin value.""" 136 index = () 137 for f in self.features: 138 for ff,value in feature_value_permutation: 139 if(ff == f.name): 140 index = index + (f.values.index(value),) 141 self.full_matrix[index] = new_values
142
143 144 145 146 # CB: FeatureResponses and ReverseCorrelation need cleanup; I began but haven't finished. 147 # JABALERT: At least: 148 # - Move features out of __init__ and into measure_responses 149 # - Change measure_responses to __call__, as it's the only thing this 150 # class really does 151 # - Make the other methods private (_) since they are for internal use 152 # - Possibly -- make the __call__ methods have the same signature? 153 # - Clean up the inheritance hierarchy? 154 155 156 -class FeatureResponses(PatternDrivenAnalysis):
157 """ 158 Systematically vary input pattern feature values and collate the responses. 159 160 Each sheet has a DistributionMatrix for each feature that will be 161 tested. The DistributionMatrix stores the distribution of 162 activity values for each unit in the sheet for that feature. For 163 instance, if the features to be tested are orientation and phase, 164 we will create a DistributionMatrix for orientation and a 165 DistributionMatrix for phase for each sheet. The orientation and 166 phase of the input are then systematically varied (when 167 measure_responses is called), and the responses of each unit 168 to each pattern are collected into the DistributionMatrix. 169 170 The resulting data can then be used to plot feature maps and 171 tuning curves, or for similar types of feature-based analyses. 172 """ 173 174 # CEB: we might want to measure the map on a sheet due 175 # to a specific projection, rather than measure the map due 176 # to all projections. 177 178 repetitions = param.Integer(default=1,bounds=(1,None),doc=""" 179 How many times each stimulus will be presented. 180 181 Each stimulus is specified by a particular feature 182 combination, and need only be presented once if the network 183 has no other source of variability. If results differ for 184 each presentation of an identical stimulus (e.g. due to 185 intrinsic noise), then this parameter can be increased 186 so that results will be an average over the specified 187 number of repetitions.""") 188 189 _fullmatrix = {} 190
191 - def __init__(self,features,**params):
196
197 - def initialize_featureresponses(self,features):
198 """Create an empty DistributionMatrix for each feature and each sheet.""" 199 self._featureresponses = {} 200 self._activities = {} 201 FeatureResponses._fullmatrix = {} 202 for sheet in self.sheets_to_measure(): 203 self._featureresponses[sheet] = {} 204 self._activities[sheet]=zeros(sheet.shape) 205 for f in features: 206 self._featureresponses[sheet][f.name]=DistributionMatrix(sheet.shape,axis_range=f.range,cyclic=f.cyclic) 207 FeatureResponses._fullmatrix[sheet] = FullMatrix(sheet.shape,features)
208
209 - def sheets_to_measure(self):
210 """Return a list of the Sheets in the current simulation for which to collect responses.""" 211 return [x for x in topo.sim.objects(Sheet).values() 212 if hasattr(x,'measure_maps') and x.measure_maps]
213
214 - def measure_responses(self,pattern_presenter,param_dict,features,display):
215 """Present the given input patterns and collate the responses.""" 216 217 # Run hooks before the analysis session 218 for f in self.pre_analysis_session_hooks: f() 219 220 self.param_dict=param_dict 221 self.pattern_presenter = pattern_presenter 222 223 features_to_permute = [f for f in features if f.compute_fn is None] 224 self.features_to_compute = [f for f in features if f.compute_fn is not None] 225 226 self.feature_names=[f.name for f in features_to_permute] 227 values_lists=[f.values for f in features_to_permute] 228 self.permutations = cross_product(values_lists) 229 values_description=' * '.join(["%d %s" % (len(f.values),f.name) for f in features_to_permute]) 230 231 self.refresh_act_wins=False 232 if display: 233 if hasattr(topo,'guimain'): 234 self.refresh_act_wins=True 235 else: 236 self.warning("No GUI available for display.") 237 238 # CEBALERT: when there are multiple sheets, this can make it seem 239 # like topographica's stuck in a loop (because the counter goes 240 # to 100% lots of times...e.g. hierarchical's orientation tuning fullfield.) 241 242 timer = copy.copy(topo.sim.timer) 243 timer.func = self.present_permutation 244 245 if hasattr(topo,'guimain'): 246 topo.guimain.open_progress_window(timer) 247 else: 248 self.verbose("Presenting %d test patterns (%s)." % (len(self.permutations),values_description)) 249 250 timer.call_fixed_num_times(self.permutations) 251 252 # Run hooks after the analysis session 253 for f in self.post_analysis_session_hooks: f()
254
255 - def present_permutation(self,permutation):
256 """Present a pattern with the specified set of feature values.""" 257 for sheet in self.sheets_to_measure(): 258 self._activities[sheet]*=0 259 260 # Calculate complete set of settings 261 permuted_settings = zip(self.feature_names, permutation) 262 complete_settings = permuted_settings + \ 263 [(f.name,f.compute_fn(permuted_settings)) for f in self.features_to_compute] 264 265 266 for i in xrange(0,self.repetitions): 267 topo.sim.state_push() 268 269 # Run hooks before and after pattern presentation. 270 # Could use complete_settings here, to avoid some 271 # PatternPresenter special cases, but that might cause 272 # conflicts with the existing PatternPresenter code. 273 for f in self.pre_presentation_hooks: f() 274 #valstring = " ".join(["%s=%s" % (n,v) for n,v in complete_settings]) 275 #self.message("Presenting pattern %s" % valstring) 276 self.pattern_presenter(dict(permuted_settings),self.param_dict) 277 for f in self.post_presentation_hooks: f() 278 279 if self.refresh_act_wins:topo.guimain.refresh_activity_windows() 280 for sheet in self.sheets_to_measure(): 281 self._activities[sheet]+=sheet.activity 282 topo.sim.state_pop() 283 284 for sheet in self.sheets_to_measure(): 285 self._activities[sheet]=self._activities[sheet] / self.repetitions 286 287 self._update(complete_settings)
288
289 - def _update(self,current_values):
290 # Update each DistributionMatrix with (activity,bin) 291 for sheet in self.sheets_to_measure(): 292 for feature,value in current_values: 293 self._featureresponses[sheet][feature].update(self._activities[sheet], value) 294 FeatureResponses._fullmatrix[sheet].update(self._activities[sheet],current_values)
295
296 297 298 -class ReverseCorrelation(FeatureResponses):
299 """ 300 Calculate the receptive fields for all neurons using reverse correlation. 301 """ 302 # CB: Can't we have a better class hierarchy? 303 304 input_sheet = param.Parameter(default=None) 305 306 # JABALERT: Should _featureresponses be renamed here?; It's a different 307 # data structure using different indexing (r,c instead of feature).
308 - def initialize_featureresponses(self,features): # CB: doesn't need features!
309 310 self._featureresponses = {} 311 assert hasattr(self.input_sheet,'shape') 312 313 # surely there's a way to get an array of 0s for each element without 314 # looping? (probably had same question for distributionmatrix). 315 for sheet in self.sheets_to_measure(): 316 self._featureresponses[sheet]= numpy.ones(sheet.activity.shape,dtype=object) 317 rows,cols = sheet.activity.shape 318 for r in range(rows): 319 for c in range(cols): 320 self._featureresponses[sheet][r,c] = numpy.zeros(self.input_sheet.shape) # need to specify dtype?
321 322
323 - def collect_feature_responses(self,pattern_presenter,param_dict,display,feature_values):
324 self.measure_responses(pattern_presenter,param_dict,feature_values,display) 325 326 for sheet in self.sheets_to_measure(): 327 rows,cols = sheet.activity.shape 328 input_bounds = self.input_sheet.bounds 329 input_sheet_views = self.input_sheet.sheet_views 330 331 for ii in range(rows): 332 for jj in range(cols): 333 view = SheetView((self._featureresponses[sheet][ii,jj],input_bounds), 334 sheet.name,sheet.precedence,topo.sim.time(),sheet.row_precedence) 335 x,y = sheet.matrixidx2sheet(ii,jj) 336 key = ('RFs',sheet.name,x,y) 337 input_sheet_views[key]=view
338 339 340 341
342 - def measure_responses(self,pattern_presenter,param_dict,features,display):
343 """Present the given input patterns and collate the responses.""" 344 345 # Since input_sheet's not fixed, we have to call this. Means that there are 346 # normally duplicate calls (e.g. gets called by __init__ and then gets called 347 # here for no reason except maybe the input_sheet got changed). Would be better 348 # to have the input_sheet fixed. 349 self.initialize_featureresponses(features) 350 351 super(ReverseCorrelation,self).measure_responses(pattern_presenter,param_dict, 352 features,display)
353
354 - def present_permutation(self,permutation):
355 """Present a pattern with the specified set of feature values.""" 356 357 # Calculate complete set of settings 358 permuted_settings = zip(self.feature_names, permutation) 359 complete_settings = permuted_settings + \ 360 [(f.name,f.compute_fn(permuted_settings)) for f in self.features_to_compute] 361 362 topo.sim.state_push() 363 364 # Run hooks before and after pattern presentation. 365 # Could use complete_settings here, to avoid some 366 # PatternPresenter special cases, but that might cause 367 # conflicts with the existing PatternPresenter code. 368 for f in self.pre_presentation_hooks: f() 369 #valstring = " ".join(["%s=%s" % (n,v) for n,v in complete_settings]) 370 #self.message("Presenting pattern %s" % valstring) 371 self.pattern_presenter(dict(permuted_settings),self.param_dict) 372 for f in self.post_presentation_hooks: f() 373 374 if self.refresh_act_wins:topo.guimain.refresh_activity_windows() 375 376 self._update(complete_settings) 377 378 topo.sim.state_pop()
379 380 # Ignores current_values; they simply provide distinct patterns on the retina
381 - def _update(self,current_values):
382 for sheet in self.sheets_to_measure(): 383 rows,cols = sheet.activity.shape 384 for ii in range(rows): 385 for jj in range(cols): 386 self._featureresponses[sheet][ii,jj]+=sheet.activity[ii,jj]*self.input_sheet.activity
387
388 389 -class FeatureMaps(FeatureResponses):
390 """ 391 Measures and collects the responses to a set of features for calculating feature maps. 392 393 For each feature and each sheet, the results are stored as a 394 preference matrix and selectivity matrix in the sheet's 395 sheet_views; these can then be plotted as preference 396 or selectivity maps. 397 """ 398 399 selectivity_multiplier = param.Number(default=17,bounds=(0.0,None),doc=""" 400 Factor by which to multiply the calculated selectivity values 401 before plotting them. Usually set much greater than 1.0 to 402 highlight particularly unselective areas, especially when 403 combining selectivity with other plots as when using Confidence 404 subplots.""") 405 406 # CBENHANCEMENT: could allow full control over the generated names 407 # using a format parameter. The default would be 408 # ${prefix}${feature}${type} (where type is Preference or 409 # Selectivity) 410 sheet_views_prefix = param.String(default="",doc=""" 411 Prefix to add to the name under which results are stored in 412 sheet_views.""") 413
414 - def __init__(self,features,**params):
415 super(FeatureMaps,self).__init__(features,**params) 416 self.features=features
417
418 - def collect_feature_responses(self,pattern_presenter,param_dict,display,weighted_average=True):
419 """ 420 Present the given input patterns and collate the responses. 421 422 If weighted_average is True, the feature responses are 423 calculated from a weighted average of the values of each bin 424 in the distribution, rather than simply using the actual value 425 of the parameter for which response was maximal (the discrete 426 method). Such a computation will generally produce much more 427 precise maps using fewer test stimuli than the discrete 428 method. However, weighted_average methods generally require 429 uniform and full-range sampling, as described below, which is 430 not always feasible. 431 432 For measurements at evenly-spaced intervals over the full 433 range of possible parameter values, weighted_averages are a 434 good measure of the underlying continuous-valued parameter 435 preference, assuming that neurons are tuned broadly enough 436 (and/or sampled finely enough) that they respond to at least 437 two of the tested parameter values. This method will not 438 usually give good results when those criteria are not met, 439 i.e. if the sampling is too sparse, not at evenly-spaced 440 intervals, or does not cover the full range of possible 441 values. In such cases weighted_average should be set to 442 False, and the number of test patterns will usually need 443 to be increased instead. 444 """ 445 self.measure_responses(pattern_presenter,param_dict,self.features,display) 446 447 for sheet in self.sheets_to_measure(): 448 bounding_box = sheet.bounds 449 450 for feature in self._featureresponses[sheet].keys(): 451 ### JCHACKALERT! This is temporary to avoid the positionpref plot to shrink 452 ### Nevertheless we should think more about this (see alert in bitmap.py) 453 ### When passing a sheet_view that is not cropped to 1 in the parameter hue of hsv_to_rgb 454 ### it does not work... The normalization seems to be necessary in this case. 455 ### I guess it is always cyclic value that we will color with hue in an hsv plot 456 ### but still we should catch the error. 457 ### Also, what happens in case of negative values? 458 # CB: (see also ALERT by SheetView's norm_factor.) 459 #JL: Should be able to get rid of norm factor and incorporate with value_multiplier 460 #should also add similar method for selectivity_offset and selectivity_multiplier 461 cyclic = self._featureresponses[sheet][feature].distribution_matrix[0,0].cyclic 462 if cyclic: 463 norm_factor = self._featureresponses[sheet][feature].distribution_matrix[0,0].axis_range 464 else: 465 norm_factor = 1.0 466 467 468 value_offset = [f.value_offset for f in self.features if f.name==feature] 469 value_multiplier = [f.value_multiplier for f in self.features if f.name==feature] 470 471 472 if weighted_average: 473 474 preference_map = SheetView((((self._featureresponses[sheet][feature].weighted_average())+value_offset)*value_multiplier/norm_factor, 475 bounding_box), sheet.name, sheet.precedence, topo.sim.time()) 476 477 else: 478 preference_map = SheetView((((self._featureresponses[sheet][feature].max_value_bin())+value_offset)*value_multiplier/norm_factor, 479 bounding_box), sheet.name, sheet.precedence, topo.sim.time()) 480 481 preference_map.cyclic = cyclic 482 preference_map.norm_factor = norm_factor 483 484 485 sheet.sheet_views[self.sheet_views_prefix+feature.capitalize()+'Preference']=preference_map 486 487 selectivity_map = SheetView((self.selectivity_multiplier* 488 self._featureresponses[sheet][feature].selectivity(), 489 bounding_box), sheet.name , sheet.precedence, topo.sim.time(),sheet.row_precedence) 490 sheet.sheet_views[self.sheet_views_prefix+feature.capitalize()+'Selectivity']=selectivity_map
491
492 493 -class FeatureCurves(FeatureResponses):
494 """ 495 Measures and collects the responses to a set of features, for calculating tuning and similar curves. 496 497 These curves represent the response of a Sheet to patterns that 498 are controlled by a set of features. This class can collect data 499 for multiple curves, each with the same x axis. The x axis 500 represents the main feature value that is being varied, such as 501 orientation. Other feature values can also be varied, such as 502 contrast, which will result in multiple curves (one per unique 503 combination of other feature values). 504 505 The sheet responses used to construct the curves will be stored in 506 a dictionary curve_dict kept in the Sheet of interest. A 507 particular set of patterns is then constructed using a 508 user-specified PatternPresenter by adding the parameters 509 determining the curve (curve_param_dict) to a static list of 510 parameters (param_dict), and then varying the specified set of 511 features. The results can be accessed in the curve_dict, 512 indexed by the curve_label and feature value. 513 """ 514 post_collect_responses_hook = param.HookList(default=[],instantiate=False,doc=""" 515 List of callable objects to be run at the end of collect_feature_responses function. 516 The functions should accept three parameters: FullMatrix, curve label, sheet""") 517
518 - def __init__(self,features,sheet,x_axis):
519 super(FeatureCurves, self).__init__(features) 520 self.sheet=sheet 521 self.x_axis=x_axis 522 if hasattr(sheet, "curve_dict")==False: 523 sheet.curve_dict={} 524 sheet.curve_dict[x_axis]={}
525
526 - def sheets_to_measure(self):
527 return topo.sim.objects(CFSheet).values()
528
529 - def collect_feature_responses(self,features,pattern_presenter,param_dict,curve_label,display):
530 self.initialize_featureresponses(features) 531 rows,cols=self.sheet.shape 532 bounding_box = self.sheet.bounds 533 self.measure_responses(pattern_presenter,param_dict,features,display) 534 self.sheet.curve_dict[self.x_axis][curve_label]={} 535 for key in self._featureresponses[self.sheet][self.x_axis].distribution_matrix[0,0]._data.iterkeys(): 536 y_axis_values = zeros(self.sheet.shape,activity_type) 537 for i in range(rows): 538 for j in range(cols): 539 y_axis_values[i,j] = self._featureresponses[self.sheet][self.x_axis].distribution_matrix[i,j].get_value(key) 540 Response = SheetView((y_axis_values,bounding_box), self.sheet.name , self.sheet.precedence, topo.sim.time(),self.sheet.row_precedence) 541 self.sheet.curve_dict[self.x_axis][curve_label].update({key:Response}) 542 for f in self.post_collect_responses_hook: f(self._fullmatrix[self.sheet],curve_label,self.sheet)
543
544 ############################################################################### 545 ############################################################################### 546 547 # Define user-level commands and helper classes for calling the above 548 549 550 -class Feature(object):
551 """ 552 Stores the parameters required for generating a map of one input feature. 553 """ 554
555 - def __init__(self, name, range=None, step=0.0, values=None, cyclic=False, value_offset=0.0, value_multiplier=1.0, compute_fn=None, offset=0, keep_peak=True):
556 """ 557 Users can provide either a range and a step size, or a list of values. 558 If a list of values is supplied, the range can be omitted unless the 559 default of the min and max in the list of values is not appropriate. 560 561 If non-None, the compute_fn should be a function that when given a list 562 of other parameter values, computes and returns the value for this feature. 563 564 If supplied, the offset is added to the given or computed values to allow 565 the starting value to be specified. 566 """ 567 self.name=name 568 self.cyclic=cyclic 569 self.compute_fn=compute_fn 570 self.range=range 571 self.keep_peak=keep_peak 572 self.value_offset=value_offset 573 self.value_multiplier=value_multiplier 574 575 if values is not None: 576 self.values=values if offset == 0 else [v+offset for v in values] 577 if not self.range: 578 self.range=(min(self.values),max(self.values)) 579 else: 580 if range is None: 581 raise ValueError('The range or values must be specified.') 582 low_bound,up_bound = self.range 583 values=(frange(low_bound,up_bound,step,not cyclic)) 584 self.values = values if offset == 0 else \ 585 [(v+offset)%(up_bound-low_bound) if cyclic else (v+offset) 586 for v in values]
587
588 589 -class PatternPresenter(param.Parameterized):
590 """ 591 Function object for presenting PatternGenerator-created patterns. 592 593 This class helps coordinate a set of patterns to be presented to a 594 set of GeneratorSheets. It provides a standardized way of 595 generating a set of linked patterns for testing or analysis, such 596 as when measuring preference maps or presenting test patterns. 597 Subclasses can provide additional mechanisms for doing this in 598 different ways. 599 """ 600 601 # JABALERT: Needs documenting, and probably also a clearer name 602 contrast_parameter = param.Parameter('michelson_contrast') 603 604 # JABALERT: Needs documenting; apparently only for retinotopy? 605 divisions = param.Parameter() 606 607 apply_output_fns = param.Boolean(default=True, doc=""" 608 When presenting a pattern, whether to apply each sheet's 609 output function. If False, for many networks the response 610 will be linear, which requires fewer test patterns to measure 611 a meaningful response, but it may not correspond to the actual 612 preferences of each neuron under other conditions. If True, 613 callers will need to ensure that the input patterns are in a 614 suitable range to drive the neurons to generate meaningful 615 output, because e.g. a threshold-based output function might 616 result in no activity for inputs that are too weak..""") 617 618 duration = param.Number(default=1.0,doc=""" 619 Amount of simulation time for which to present each test pattern. 620 By convention, most Topographica example files are designed to 621 have a suitable activity pattern computed by the 622 default time, but the duration will need to be changed for 623 other models that do not follow that convention or if a 624 linear response is desired.""") 625 626 # CEBALERT: generator_sheets=[] is probably a surprising way of 627 # actually getting all the generator sheets. 628 generator_sheets = param.List(default=[], doc=""" 629 The set of GeneratorSheets onto which patterns will be drawn. 630 631 By default (i.e. for an empty list), all GeneratorSheets in 632 the simulation will be used. 633 """) 634
635 - def __init__(self,pattern_generator,**params):
636 """ 637 pattern_generator is the PatternGenerator that will be drawn 638 on the generator_sheets (the parameters of the 639 pattern_generator are specified during calls. 640 """ 641 super(PatternPresenter,self).__init__(**params) 642 self.gen = pattern_generator # Why not a Parameter?
643 644
645 - def __call__(self,features_values,param_dict):
646 for param,value in param_dict.iteritems(): 647 # CEBALERT: why not setattr(self.gen,param,value) 648 #if ('_'+param+'_param_value') not in self.gen.__dict__: 649 self.gen.__setattr__(param,value) 650 651 for feature,value in features_values.iteritems(): 652 self.gen.__setattr__(feature,value) 653 654 all_input_sheet_names = topo.sim.objects(GeneratorSheet).keys() 655 656 if len(self.generator_sheets)>0: 657 input_sheet_names = [sheet.name for sheet in self.generator_sheets] 658 else: 659 input_sheet_names = all_input_sheet_names 660 661 # Copy the given generator once for every GeneratorSheet 662 inputs = dict.fromkeys(input_sheet_names) 663 for k in inputs.keys(): 664 inputs[k]=copy.deepcopy(self.gen) 665 666 ### JABALERT: Should replace these special cases with general 667 ### support for having meta-parameters controlling the 668 ### generation of different patterns for each GeneratorSheet. 669 ### For instance, we will also need to support xdisparity and 670 ### ydisparity, plus movement of patterns between two eyes, colors, 671 ### etc. At the very least, it should be simple to control 672 ### differences in single parameters easily. In addition, 673 ### these meta-parameters should show up as parameters for 674 ### this object, augmenting the parameters for each individual 675 ### pattern, e.g. in the Test Pattern window. In this way we 676 ### should be able to provide general support for manipulating 677 ### both pattern parameters and parameters controlling 678 ### interaction between or differences between patterns. 679 680 if 'direction' in features_values: 681 import __main__ 682 if '_new_motion_model' in __main__.__dict__ and __main__.__dict__['_new_motion_model']: 683 #### new motion model #### 684 from topo.pattern import Translator 685 for name in inputs: 686 inputs[name] = Translator(generator=inputs[name], 687 direction=features_values['direction'], 688 speed=features_values['speed'], 689 reset_period=self.duration) 690 ########################## 691 else: 692 #### old motion model #### 693 orientation = features_values['direction']+pi/2 694 from topo.pattern.basic import Sweeper 695 for name in inputs.keys(): 696 speed=features_values['speed'] 697 try: 698 step=int(name[-1]) 699 except: 700 if not hasattr(self,'direction_warned'): 701 self.warning('Assuming step is zero; no input lag number specified at the end of the input sheet name.') 702 self.direction_warned=True 703 step=0 704 speed=features_values['speed'] 705 inputs[name] = Sweeper(generator=inputs[name],step=step,speed=speed) 706 setattr(inputs[name],'orientation',orientation) 707 ########################## 708 709 if features_values.has_key('hue'): 710 711 # could be three retinas (R, G, and B) or a single RGB 712 # retina for the color dimension; if every retina has 713 # 'Red' or 'Green' or 'Blue' in its name, then three 714 # retinas for color are assumed 715 716 rgb_retina = False 717 for name in input_sheet_names: 718 if not ('Red' in name or 'Green' in name or 'Blue' in name): 719 rgb_retina=True 720 721 if not rgb_retina: 722 for name in inputs.keys(): 723 r,g,b=hsv_to_rgb(features_values['hue'],1.0,1.0) 724 if (name.count('Red')): 725 inputs[name].scale=r 726 elif (name.count('Green')): 727 inputs[name].scale=g 728 elif (name.count('Blue')): 729 inputs[name].scale=b 730 else: 731 if not hasattr(self,'hue_warned'): 732 self.warning('Unable to measure hue preference, because hue is defined only when there are different input sheets with names with Red, Green or Blue substrings.') 733 self.hue_warned=True 734 else: 735 from contrib import rgbimages 736 r,g,b=hsv_to_rgb(features_values['hue'],1.0,1.0) 737 for name in inputs.keys(): 738 inputs[name] = rgbimages.ExtendToRGB(generator=inputs[name], 739 relative_channel_strengths=[r,g,b]) 740 # CEBALERT: should warn as above if not a color network 741 742 #JL: This is only used for retinotopy measurement in jude laws contrib/jsldefs.py 743 #Also needs cleaned up 744 if features_values.has_key('retinotopy'): 745 #Calculates coordinates of the center of each patch to be presented 746 coordinate_x=[] 747 coordinate_y=[] 748 coordinates=[] 749 for name,i in zip(inputs.keys(),range(len(input_sheet_names))): 750 l,b,r,t = topo.sim[name].nominal_bounds.lbrt() 751 x_div=float(r-l)/(self.divisions*2) 752 y_div=float(t-b)/(self.divisions*2) 753 for i in range(self.divisions): 754 if not bool(self.divisions%2): 755 if bool(i%2): 756 coordinate_x.append(i*x_div) 757 coordinate_y.append(i*y_div) 758 coordinate_x.append(i*-x_div) 759 coordinate_y.append(i*-y_div) 760 else: 761 if not bool(i%2): 762 coordinate_x.append(i*x_div) 763 coordinate_y.append(i*y_div) 764 coordinate_x.append(i*-x_div) 765 coordinate_y.append(i*-y_div) 766 for x in coordinate_x: 767 for y in coordinate_y: 768 coordinates.append((x,y)) 769 770 x_coord=coordinates[features_values['retinotopy']][0] 771 y_coord=coordinates[features_values['retinotopy']][1] 772 inputs[name].x = x_coord 773 inputs[name].y = y_coord 774 775 if features_values.has_key('retx'): 776 for name,i in zip(inputs.keys(),range(len(input_sheet_names))): 777 inputs[name].x = features_values['retx'] 778 inputs[name].y = features_values['rety'] 779 780 if features_values.has_key("phasedisparity"): 781 temp_phase1=features_values['phase']-features_values['phasedisparity']/2.0 782 temp_phase2=features_values['phase']+features_values['phasedisparity']/2.0 783 for name in inputs.keys(): 784 if (name.count('Right')): 785 inputs[name].phase=wrap(0,2*pi,temp_phase1) 786 elif (name.count('Left')): 787 inputs[name].phase=wrap(0,2*pi,temp_phase2) 788 else: 789 if not hasattr(self,'disparity_warned'): 790 self.warning('Unable to measure disparity preference, because disparity is defined only when there are inputs for Right and Left retinas.') 791 self.disparity_warned=True 792 793 ## Not yet used; example only 794 #if features_values.has_key("xdisparity"): 795 # if len(input_sheet_names)!=2: 796 # self.warning('Disparity is defined only when there are exactly two patterns') 797 # else: 798 # inputs[input_sheet_names[0]].x=inputs[input_sheet_names[0]].x - inputs[input_sheet_names[0]].xdisparity/2.0 799 # inputs[input_sheet_names[1]].x=inputs[input_sheet_names[1]].x + inputs[input_sheet_names[1]].xdisparity/2.0 800 # 801 # inputs={} 802 # inputs[input_sheet_names[0]]=inputs[input_sheet_names[0]] 803 # inputs[input_sheet_names[1]]=inputs[input_sheet_names[1]] 804 # 805 #if features_values.has_key("ydisparity"): 806 # if len(input_sheet_names)!=2: 807 # self.warning('Disparity is defined only when there are exactly two patterns') 808 # else: 809 # inputs[input_sheet_names[0]].y=inputs[input_sheet_names[0]].y - inputs[input_sheet_names[0]].ydisparity/2.0 810 # inputs[input_sheet_names[1]].y=inputs[input_sheet_names[1]].y + inputs[input_sheet_names[1]].ydisparity/2.0 811 # 812 # inputs={} 813 # inputs[input_sheet_names[0]]=inputs[input_sheet_names[0]] 814 # inputs[input_sheet_names[1]]=inputs[input_sheet_names[1]] 815 816 if features_values.has_key("ocular"): 817 for name in inputs.keys(): 818 if (name.count('Right')): 819 inputs[name].scale=2*features_values['ocular'] 820 elif (name.count('Left')): 821 inputs[name].scale=2.0-2*features_values['ocular'] 822 else: 823 self.warning('Skipping input region %s; Ocularity is defined only for Left and Right retinas.' % 824 name) 825 826 if features_values.has_key("contrastcenter")or param_dict.has_key("contrastcenter"): 827 if self.contrast_parameter=='michelson_contrast': 828 for g in inputs.itervalues(): 829 g.offsetcenter=0.5 830 g.scalecenter=2*g.offsetcenter*g.contrastcenter/100.0 831 832 elif self.contrast_parameter=='weber_contrast': 833 # Weber_contrast is currently only well defined for 834 # the special case where the background offset is equal 835 # to the target offset in the pattern type 836 # SineGrating(mask_shape=Disk()) 837 for g in inputs.itervalues(): 838 g.offsetcenter=0.5 #In this case this is the offset of both the background and the sine grating 839 g.scalecenter=2*g.offsetcenter*g.contrastcenter/100.0 840 841 elif self.contrast_parameter=='scale': 842 for g in inputs.itervalues(): 843 g.offsetcenter=0.0 844 g.scalecenter=g.contrastcenter 845 846 if features_values.has_key("contrastsurround")or param_dict.has_key("contrastsurround"): 847 if self.contrast_parameter=='michelson_contrast': 848 for g in inputs.itervalues(): 849 g.offsetsurround=0.5 850 g.scalesurround=2*g.offsetsurround*g.contrastsurround/100.0 851 852 elif self.contrast_parameter=='weber_contrast': 853 # Weber_contrast is currently only well defined for 854 # the special case where the background offset is equal 855 # to the target offset in the pattern type 856 # SineGrating(mask_shape=Disk()) 857 for g in inputs.itervalues(): 858 g.offsetsurround=0.5 #In this case this is the offset of both the background and the sine grating 859 g.scalesurround=2*g.offsetsurround*g.contrastsurround/100.0 860 861 elif self.contrast_parameter=='scale': 862 for g in inputs.itervalues(): 863 g.offsetsurround=0.0 864 g.scalesurround=g.contrastsurround 865 866 if features_values.has_key("contrast") or param_dict.has_key("contrast"): 867 if self.contrast_parameter=='michelson_contrast': 868 for g in inputs.itervalues(): 869 g.offset=0.5 870 g.scale=2*g.offset*g.contrast/100.0 871 872 elif self.contrast_parameter=='weber_contrast': 873 # Weber_contrast is currently only well defined for 874 # the special case where the background offset is equal 875 # to the target offset in the pattern type 876 # SineGrating(mask_shape=Disk()) 877 for g in inputs.itervalues(): 878 g.offset=0.5 #In this case this is the offset of both the background and the sine grating 879 g.scale=2*g.offset*g.contrast/100.0 880 881 elif self.contrast_parameter=='scale': 882 for g in inputs.itervalues(): 883 g.offset=0.0 884 g.scale=g.contrast 885 886 # blank patterns for unused generator sheets 887 for sheet_name in set(all_input_sheet_names).difference(set(input_sheet_names)): 888 inputs[sheet_name]=pattern.Constant(scale=0) 889 890 pattern_present(inputs, self.duration, plastic=False, 891 apply_output_fns=self.apply_output_fns)
892
893 894 -class Subplotting(param.Parameterized):
895 """ 896 Convenience functions for handling subplots (such as colorized Activity plots). 897 Only needed for avoiding typing, as plots can be declared with their own 898 specific subplots without using these functions. 899 """ 900 901 plotgroups_to_subplot=param.List(default= 902 ["Activity", "Connection Fields", "Projection", "Projection Activity"], 903 doc="List of plotgroups for which to set subplots.") 904 905 subplotting_declared = param.Boolean(default=False, 906 doc="Whether set_subplots has previously been called") 907 908 _last_args = param.Parameter(default=()) 909 910 @staticmethod
911 - def set_subplots(prefix=None,hue="",confidence="",force=True):
912 """ 913 Define Hue and Confidence subplots for each of the plotgroups_to_subplot. 914 Typically used to make activity or weight plots show a 915 preference value as the hue, and a selectivity as the 916 confidence. 917 918 The specified hue, if any, should be the name of a SheetView, 919 such as OrientationPreference. The specified confidence, if 920 any, should be the name of a (usually) different SheetView, 921 such as OrientationSelectivity. 922 923 The prefix option is a shortcut making the usual case easier 924 to type; it sets hue to prefix+"Preference" and confidence to 925 prefix+"Selectivity". 926 927 If force=False, subplots are changed only if no subplot is 928 currently defined. Force=False is useful for setting up 929 subplots automatically when maps are measured, without 930 overwriting any subplots set up specifically by the user. 931 932 Currently works only for plotgroups that have a plot 933 with the same name as the plotgroup, though this could 934 be changed easily. 935 936 Examples:: 937 938 Subplotting.set_subplots("Orientation") 939 - Set the default subplots to OrientationPreference and OrientationSelectivity 940 941 Subplotting.set_subplots(hue="OrientationPreference") 942 - Set the default hue subplot to OrientationPreference with no selectivity 943 944 Subplotting.set_subplots() 945 - Remove subplots from all the plotgroups_to_subplot. 946 """ 947 948 Subplotting._last_args=(prefix,hue,confidence,force) 949 950 if Subplotting.subplotting_declared and not force: 951 return 952 953 if prefix: 954 hue=prefix+"Preference" 955 confidence=prefix+"Selectivity" 956 957 for name in Subplotting.plotgroups_to_subplot: 958 if plotgroups.has_key(name): 959 pg=plotgroups[name] 960 if pg.plot_templates.has_key(name): 961 pt=pg.plot_templates[name] 962 pt["Hue"]=hue 963 pt["Confidence"]=confidence 964 else: 965 Subplotting().warning("No template %s defined for plotgroup %s" % (name,name)) 966 else: 967 Subplotting().warning("No plotgroup %s defined" % name) 968 969 Subplotting.subplotting_declared=True
970 971 972 @staticmethod
973 - def restore_subplots():
976
977 978 979 ############################################################################### 980 ############################################################################### 981 ############################################################################### 982 # 983 # 20081017 JABNOTE: This implementation could be improved. 984 # 985 # It currently requires every subclass to implement the feature_list 986 # method, which constructs a list of features using various parameters 987 # to determine how many and which values each feature should have. It 988 # would be good to replace the feature_list method with a Parameter or 989 # set of Parameters, since it is simply a special data structure, and 990 # this would make more detailed control feasible for users. For 991 # instance, instead of something like num_orientations being used to 992 # construct the orientation Feature, the user could specify the 993 # appropriate Feature directly, so that they could e.g. supply a 994 # specific list of orientations instead of being limited to a fixed 995 # spacing. 996 # 997 # However, when we implemented this, we ran into two problems: 998 # 999 # 1. It's difficult for users to modify an open-ended list of 1000 # Features. E.g., if features is a List: 1001 # 1002 # features=param.List(doc="List of Features to vary""",default=[ 1003 # Feature(name="frequency",values=[2.4]), 1004 # Feature(name="orientation",range=(0.0,pi),step=pi/4,cyclic=True), 1005 # Feature(name="phase",range=(0.0,2*pi),step=2*pi/18,cyclic=True)]) 1006 # 1007 # then it it's easy to replace the entire list, but tough to 1008 # change just one Feature. Of course, features could be a 1009 # dictionary, but that doesn't help, because when the user 1010 # actually calls the function, they want the arguments to 1011 # affect only that call, whereas looking up the item in a 1012 # dictionary would only make permanent changes easy, not 1013 # single-call changes. 1014 # 1015 # Alternatively, one could make each feature into a separate 1016 # parameter, and then collect them using a naming convention like: 1017 # 1018 # def feature_list(self,p): 1019 # fs=[] 1020 # for n,v in self.get_param_values(): 1021 # if n in p: v=p[n] 1022 # if re.match('^[^_].*_feature$',n): 1023 # fs+=[v] 1024 # return fs 1025 # 1026 # But that's quite hacky, and doesn't solve problem 2. 1027 # 1028 # 2. Even if the users can somehow access each Feature, the same 1029 # problem occurs for the individual parts of each Feature. E.g. 1030 # using the separate feature parameters above, Spatial Frequency 1031 # map measurement would require: 1032 # 1033 # from topo.command.analysis import Feature 1034 # from math import pi 1035 # pre_plot_hooks=[measure_or_pref.instance( \ 1036 # frequency_feature=Feature(name="frequency",values=frange(1.0,6.0,0.2)), \ 1037 # phase_feature=Feature(name="phase",range=(0.0,2*pi),step=2*pi/15,cyclic=True), \ 1038 # orientation_feature=Feature(name="orientation",range=(0.0,pi),step=pi/4,cyclic=True)]) 1039 # 1040 # rather than the current, much more easily controllable implementation: 1041 # 1042 # pre_plot_hooks=[measure_or_pref.instance(frequencies=frange(1.0,6.0,0.2),\ 1043 # num_phase=15,num_orientation=4)] 1044 # 1045 # I.e., to change anything about a Feature, one has to supply an 1046 # entirely new Feature, because otherwise the original Feature 1047 # would be changed for all future calls. Perhaps there's some way 1048 # around this by copying objects automatically at the right time, 1049 # but if so it's not obvious. Meanwhile, the current 1050 # implementation is reasonably clean and easy to use, if not as 1051 # flexible as it could be. 1052 1053 1054 1055 -class MeasureResponseCommand(ParameterizedFunction):
1056 """Parameterized command for presenting input patterns and measuring responses.""" 1057 1058 scale = param.Number(default=1.0,softbounds=(0.0,2.0),doc=""" 1059 Multiplicative strength of input pattern.""") 1060 1061 offset = param.Number(default=0.0,softbounds=(-1.0,1.0),doc=""" 1062 Additive offset to input pattern.""") 1063 1064 display = param.Boolean(default=False,doc=""" 1065 Whether to update a GUI display (if any) during the map measurement.""") 1066 1067 weighted_average= param.Boolean(default=True, doc=""" 1068 Whether to compute results using a weighted average, or just 1069 discrete values. A weighted average can give more precise 1070 results, without being limited to a set of discrete values, 1071 but the results can have systematic biases due to the 1072 averaging, especially for non-cyclic parameters.""") 1073 1074 pattern_presenter = param.Callable(default=None,instantiate=True,doc=""" 1075 Callable object that will present a parameter-controlled pattern to a 1076 set of Sheets. Needs to be supplied by a subclass or in the call. 1077 The attributes duration and apply_output_fns (if non-None) will 1078 be set on this object, and it should respect those if possible.""") 1079 1080 static_parameters = param.List(class_=str,default=["scale","offset"],doc=""" 1081 List of names of parameters of this class to pass to the 1082 pattern_presenter as static parameters, i.e. values that 1083 will be fixed to a single value during measurement.""") 1084 1085 subplot = param.String("",doc="""Name of map to register as a subplot, if any.""") 1086 1087 apply_output_fns = param.Boolean(default=None, doc=""" 1088 If non-None, pattern_presenter.apply_output_fns will be 1089 set to this value. Provides a simple way to set 1090 this commonly changed option of PatternPresenter.""") 1091 1092 duration = param.Number(default=None,doc=""" 1093 If non-None, pattern_presenter.duration will be 1094 set to this value. Provides a simple way to set 1095 this commonly changed option of PatternPresenter.""") 1096 1097 sheet_views_prefix = param.String(default="",doc=""" 1098 Optional prefix to add to the name under which results are 1099 stored in sheet_views. Can be used e.g. to distinguish maps as 1100 originating from a particular GeneratorSheet.""") 1101 1102 generator_sheets = param.List(default=[],doc=""" 1103 pattern_presenter.generator_sheets will be set to this value. 1104 The default value of [] results in all GeneratorSheets being 1105 used.""") 1106 1107 __abstract = True 1108 1109
1110 - def __call__(self,**params):
1111 """Measure the response to the specified pattern and store the data in each sheet.""" 1112 p=ParamOverrides(self,params) 1113 x=FeatureMaps(self._feature_list(p),name="FeatureMaps_for_"+self.name, 1114 sheet_views_prefix=p.sheet_views_prefix) 1115 static_params = dict([(s,p[s]) for s in p.static_parameters]) 1116 if p.duration is not None: 1117 p.pattern_presenter.duration=p.duration 1118 if p.apply_output_fns is not None: 1119 p.pattern_presenter.apply_output_fns=p.apply_output_fns 1120 p.pattern_presenter.generator_sheets=p.generator_sheets 1121 1122 x.collect_feature_responses(p.pattern_presenter,static_params, 1123 p.display,p.weighted_average) 1124 1125 if p.subplot != "": 1126 Subplotting.set_subplots(p.subplot,force=True) 1127 1128 return x._fullmatrix
1129 1130
1131 - def _feature_list(self,p):
1132 """Return the list of features to vary; must be implemented by each subclass.""" 1133 raise NotImplementedError
1134
1135 1136 1137 -class SinusoidalMeasureResponseCommand(MeasureResponseCommand):
1138 """Parameterized command for presenting sine gratings and measuring responses.""" 1139 1140 pattern_presenter = param.Callable(instantiate=True, 1141 default=PatternPresenter(pattern_generator=SineGrating()),doc=""" 1142 Callable object that will present a parameter-controlled pattern to a 1143 set of Sheets. By default, uses a SineGrating presented for a short 1144 duration. By convention, most Topographica example files 1145 are designed to have a suitable activity pattern computed by 1146 that time, but the duration will need to be changed for other 1147 models that do not follow that convention.""") 1148 1149 frequencies = param.List(class_=float,default=[2.4],doc="Sine grating frequencies to test.") 1150 1151 num_phase = param.Integer(default=18,bounds=(1,None),softbounds=(1,48), 1152 doc="Number of phases to test.") 1153 1154 num_orientation = param.Integer(default=4,bounds=(1,None),softbounds=(1,24), 1155 doc="Number of orientations to test.") 1156 1157 scale = param.Number(default=0.3) 1158 1159 __abstract = True
1160
1161 1162 1163 -class PositionMeasurementCommand(MeasureResponseCommand):
1164 """Parameterized command for measuring topographic position.""" 1165 1166 divisions=param.Integer(default=6,bounds=(1,None),doc=""" 1167 The number of different positions to measure in X and in Y.""") 1168 1169 x_range=param.NumericTuple((-0.5,0.5),doc=""" 1170 The range of X values to test.""") 1171 1172 y_range=param.NumericTuple((-0.5,0.5),doc=""" 1173 The range of Y values to test.""") 1174 1175 size=param.Number(default=0.5,bounds=(0,None),doc=""" 1176 The size of the pattern to present.""") 1177 1178 pattern_presenter = param.Callable( 1179 default=PatternPresenter(Gaussian(aspect_ratio=1.0)),doc=""" 1180 Callable object that will present a parameter-controlled 1181 pattern to a set of Sheets. For measuring position, the 1182 pattern_presenter should be spatially localized, yet also able 1183 to activate the appropriate neurons reliably.""") 1184 1185 static_parameters = param.List(default=["scale","offset","size"]) 1186 1187 __abstract = True
1188
1189 1190 1191 1192 -class SingleInputResponseCommand(MeasureResponseCommand):
1193 """ 1194 A callable Parameterized command for measuring the response to input on a specified Sheet. 1195 1196 Note that at present the input is actually presented to all input sheets; the 1197 specified Sheet is simply used to determine various parameters. In the future, 1198 it may be modified to draw the pattern on one input sheet only. 1199 """ 1200 # CBERRORALERT: Need to alter PatternPresenter to accept an input sheet, 1201 # to allow it to be presented on only one sheet. 1202 1203 input_sheet = param.ObjectSelector( 1204 default=None,doc=""" 1205 Name of the sheet where input should be drawn.""") 1206 1207 scale = param.Number(default=30.0) 1208 1209 offset = param.Number(default=0.5) 1210 1211 # JABALERT: Presumably the size is overridden in the call, right? 1212 pattern_presenter = param.Callable( 1213 default=PatternPresenter(RawRectangle(size=0.1,aspect_ratio=1.0))) 1214 1215 static_parameters = param.List(default=["scale","offset","size"]) 1216 1217 weighted_average = None # Disabled unused parameter 1218 1219 __abstract = True
1220
1221 1222 1223 -class FeatureCurveCommand(SinusoidalMeasureResponseCommand):
1224 """A callable Parameterized command for measuring tuning curves.""" 1225 1226 num_orientation = param.Integer(default=12) 1227 1228 sheet = param.ObjectSelector( 1229 default=None,doc=""" 1230 Name of the sheet to use in measurements.""") 1231 1232 units = param.String(default='%',doc=""" 1233 Units for labeling the curve_parameters in figure legends. 1234 The default is %, for use with contrast, but could be any 1235 units (or the empty string).""") 1236 1237 # Make constant in subclasses? 1238 x_axis = param.String(default='orientation',doc=""" 1239 Parameter to use for the x axis of tuning curves.""") 1240 1241 static_parameters = param.List(default=[]) 1242 1243 # JABALERT: Might want to accept a list of values for a given 1244 # parameter to make the simple case easier; then maybe could do 1245 # the crossproduct of them? 1246 curve_parameters=param.Parameter([{"contrast":30},{"contrast":60},{"contrast":80},{"contrast":90}],doc=""" 1247 List of parameter values for which to measure a curve.""") 1248 1249 __abstract = True 1250
1251 - def __call__(self,**params):
1252 """Measure the response to the specified pattern and store the data in each sheet.""" 1253 p=ParamOverrides(self,params) 1254 self._compute_curves(p,p.sheet)
1255 1256
1257 - def _compute_curves(self,p,sheet,val_format='%s'):
1258 """ 1259 Compute a set of curves for the specified sheet, using the 1260 specified val_format to print a label for each value of a 1261 curve_parameter. 1262 """ 1263 1264 x=FeatureCurves(self._feature_list(p),sheet=sheet,x_axis=self.x_axis) 1265 for curve in p.curve_parameters: 1266 static_params = dict([(s,p[s]) for s in p.static_parameters]) 1267 static_params.update(curve) 1268 curve_label="; ".join([('%s = '+val_format+'%s') % (n.capitalize(),v,p.units) for n,v in curve.items()]) 1269 # JABALERT: Why is the feature list duplicated here? 1270 x.collect_feature_responses(self._feature_list(p),p.pattern_presenter,static_params,curve_label,p.display)
1271 1272 1273
1274 - def _feature_list(self,p):
1275 return [Feature(name="phase",range=(0.0,2*pi),step=2*pi/p.num_phase,cyclic=True), 1276 Feature(name="orientation",range=(0,pi),step=pi/p.num_orientation,cyclic=True), 1277 Feature(name="frequency",values=p.frequencies)]
1278 1279
1280 - def _sheetview_unit(self,sheet,sheet_coord,map_name,default=0.0):
1281 """Look up and return the value of a SheetView for a specified unit.""" 1282 matrix_coords = sheet.sheet2matrixidx(*sheet_coord) 1283 1284 if(map_name in sheet.sheet_views): 1285 pref = sheet.sheet_views[map_name].view()[0] 1286 val = pref[matrix_coords] 1287 else: 1288 self.warning(("%s should be measured before plotting this tuning curve -- " + 1289 "using default value of %s for %s unit (%d,%d).") % \ 1290 (map_name,default,sheet.name,sheet_coord[0],sheet_coord[1])) 1291 val = default 1292 1293 return val
1294
1295 1296 -class UnitCurveCommand(FeatureCurveCommand):
1297 """ 1298 Measures tuning curve(s) of particular unit(s). 1299 """ 1300 1301 pattern_presenter = param.Callable( 1302 default=PatternPresenter(pattern_generator=SineGrating(mask_shape=Disk(smoothing=0.0,size=1.0)), 1303 contrast_parameter="weber_contrast")) 1304 1305 size=param.Number(default=0.5,bounds=(0,None),doc=""" 1306 The size of the pattern to present.""") 1307 1308 coords = param.List(default=[(0,0)],doc=""" 1309 List of coordinates of units to measure.""") 1310 1311 __abstract = True
1312