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

Source Code for Module topo.command.analysis

  1  """ 
  2  User-level analysis commands, typically for measuring or generating SheetViews. 
  3   
  4  Most of this file consists of commands for creating SheetViews, paired 
  5  with a template for how to construct plots from these SheetViews. 
  6   
  7  For instance, the implementation of Activity plots consists of the 
  8  update_activity() command plus the Activity PlotGroupTemplate.  The 
  9  update_activity() command reads the activity array of each Sheet and 
 10  makes a corresponding SheetView to put in the Sheet's sheet_views 
 11  dictionary, while the Activity PlotGroupTemplate specifies which 
 12  SheetViews should be plotted in which combination.  See the help for 
 13  PlotGroupTemplate for more information. 
 14   
 15  Some of the commands are ordinary Python functions, but the rest are 
 16  ParameterizedFunctions, which act like Python functions but support 
 17  Parameters with defaults, bounds, inheritance, etc.  These commands 
 18  are usually grouped together using inheritance so that they share a 
 19  set of parameters and some code, and only the bits that are specific 
 20  to that particular plot or analysis appear below.  See the 
 21  superclasses for the rest of the parameters and code. 
 22   
 23  $Id: analysis.py 11316 2010-07-27 17:52:53Z ceball $ 
 24  """ 
 25  __version__='$Revision: 11316 $' 
 26   
 27   
 28  from math import pi, sin, cos 
 29  from PIL import Image, ImageDraw 
 30  import copy 
 31   
 32  from numpy.oldnumeric import array, maximum 
 33   
 34  import param 
 35  from param.parameterized import ParameterizedFunction 
 36  from param.parameterized import ParamOverrides 
 37   
 38  import topo 
 39  from topo.base.cf import Projection 
 40  from topo.base.sheet import Sheet 
 41  from topo.base.sheetview import SheetView 
 42  from topo.misc.distribution import Distribution 
 43  from topo.pattern.basic import GaussiansCorner, RawRectangle, Line 
 44  from topo.analysis.featureresponses import ReverseCorrelation 
 45  from topo.plotting.plotgroup import create_plotgroup, plotgroups 
 46   
 47  from topo.plotting.plotgroup import UnitMeasurementCommand,ProjectionSheetMeasurementCommand 
 48  from topo.analysis.featureresponses import Feature, PatternPresenter 
 49  from topo.analysis.featureresponses import SinusoidalMeasureResponseCommand, PositionMeasurementCommand, SingleInputResponseCommand 
 50   
 51   
 52  # Helper function for save_plotgroup 
53 -def _equivalent_for_plotgroup_update(p1,p2):
54 """ 55 Helper function for save_plotgroup. 56 57 Comparison operator for deciding whether make_plots(update==False) is 58 safe for one plotgroup if the other has already been updated. 59 60 Treats plotgroups as the same if the specified list of attributes 61 (if present) match in both plotgroups. 62 """ 63 64 attrs_to_check = ['pre_plot_hooks','keyname','sheet','x','y','projection','input_sheet','density','coords'] 65 66 for a in attrs_to_check: 67 if hasattr(p1,a) or hasattr(p2,a): 68 if not (hasattr(p1,a) and hasattr(p2,a) and getattr(p1,a)== getattr(p2,a)): 69 return False 70 71 return True
72 73 74
75 -class save_plotgroup(ParameterizedFunction):
76 """ 77 Convenience command for saving a set of plots to disk. Examples: 78 79 save_plotgroup("Activity") 80 save_plotgroup("Orientation Preference") 81 save_plotgroup("Projection",projection=topo.sim['V1'].projections('Afferent')) 82 83 Some plotgroups accept optional parameters, which can be passed 84 like projection above. 85 """ 86 87 equivalence_fn = param.Callable(default=_equivalent_for_plotgroup_update,doc=""" 88 Function to call on plotgroups p1,p2 to determine if calling pre_plot_hooks 89 on one of them is sufficient to update both plots. Should return False 90 unless the commands are exact equivalents, including all relevant parameters.""") 91 92 use_cached_results = param.Boolean(default=False,doc=""" 93 If True, will use the equivalence_fn to determine cases where 94 the pre_plot_hooks for a plotgroup can safely be skipped, to 95 avoid lengthy redundant computation. Should usually be 96 False for safety, but can be enabled for e.g. batch mode 97 runs using a related batch of plots.""") 98 99 saver_params = param.Dict(default={},doc=""" 100 Optional parameters to pass to the underlying PlotFileSaver object.""") 101 102 103 # Class variables to cache values from previous invocations 104 previous_time=[-1] 105 previous_plotgroups=[] 106
107 - def __call__(self,name,**params):
108 p=ParamOverrides(self,params,allow_extra_keywords=True) 109 110 plotgroup = copy.deepcopy(plotgroups[name]) 111 112 # JABALERT: Why does a Projection plot need a Sheet parameter? 113 # CB: It shouldn't, of course, since we know the sheet when we have 114 # the projection object - it's just leftover from when we passed the 115 # names instead. There should be an ALERT already about this somewhere 116 # in projectionpanel.py or plotgroup.py (both need to be changed). 117 if 'projection' in params: 118 setattr(plotgroup,'sheet',params['projection'].dest) 119 120 plotgroup._set_name(name) 121 122 # Specified parameters that aren't parameters of 123 # save_plotgroup() are set on the plotgroup 124 for n,v in p.extra_keywords().items(): 125 plotgroup.set_param(n,v) 126 127 # Reset plot cache when time changes 128 if (topo.sim.time() != self.previous_time[0]): 129 del self.previous_time[:] 130 del self.previous_plotgroups[:] 131 self.previous_time.append(topo.sim.time()) 132 133 # Skip update step if equivalent to prior command at this sim time 134 update=True 135 if p.use_cached_results: 136 for g in self.previous_plotgroups: 137 if p.equivalence_fn(g,plotgroup): 138 update=False 139 break 140 141 keywords=" ".join(["%s" % (v.name if isinstance(v,param.Parameterized) else str(v)) for n,v in p.extra_keywords().items()]) 142 plot_description="%s%s%s" % (plotgroup.name," " if keywords else "",keywords) 143 if update: 144 self.previous_plotgroups.append(plotgroup) 145 self.debug("%s: Running pre_plot_hooks" % plot_description) 146 else: 147 self.message("%s: Using cached results from pre_plot_hooks" % plot_description) 148 149 plotgroup.make_plots(update=update) 150 plotgroup.filesaver.save_to_disk(**(p.saver_params))
151 152 153
154 -def decode_feature(sheet, preference_map = "OrientationPreference", axis_bounds=(0.0,1.0), cyclic=True, weighted_average=True):
155 """ 156 Estimate the value of a feature from the current activity pattern on a sheet. 157 158 The specified preference_map should be measured before this 159 function is called. 160 161 If weighted_average is False, the feature value returned is the 162 value of the preference_map at the maximally active location. 163 164 If weighted_average is True, the feature value is estimated by 165 weighting the preference_map by the current activity level, and 166 averaging the result across all units in the sheet. The 167 axis_bounds specify the allowable range of the feature values in 168 the preference_map. If cyclic is true, a vector average is used; 169 otherwise an arithmetic weighted average is used. 170 171 For instance, if preference_map is OrientationPreference (a cyclic 172 quantity), then the result will be the vector average of the 173 activated orientations. For an orientation map this value should 174 be an estimate of the orientation present on the input. 175 """ 176 d = Distribution(axis_bounds, cyclic) 177 178 if not (preference_map in sheet.sheet_views): 179 topo.sim.warning(preference_map + " should be measured before calling decode_orientations.") 180 else: 181 map = sheet.sheet_views[preference_map] 182 d.add(dict(zip(map.view()[0].ravel(), sheet.activity.ravel()))) 183 184 if weighted_average: 185 return d.weighted_average() 186 else: 187 return d.max_value_bin()
188 189 190
191 -def update_activity():
192 """ 193 Make a map of neural activity available for each sheet, for use in template-based plots. 194 195 This command simply asks each sheet for a copy of its activity 196 matrix, and then makes it available for plotting. Of course, for 197 some sheets providing this information may be non-trivial, e.g. if 198 they need to average over recent spiking activity. 199 """ 200 for sheet in topo.sim.objects(Sheet).values(): 201 activity_copy = array(sheet.activity) 202 new_view = SheetView((activity_copy,sheet.bounds), 203 sheet.name,sheet.precedence,topo.sim.time(),sheet.row_precedence) 204 sheet.sheet_views['Activity']=new_view
205 206 207 pg = create_plotgroup(name='Activity',category='Basic', 208 doc='Plot the activity for all Sheets.', auto_refresh=True, 209 pre_plot_hooks=[update_activity], plot_immediately=True) 210 pg.add_plot('Activity',[('Strength','Activity')]) 211 212
213 -def update_rgb_activities():
214 """ 215 Make available Red, Green, and Blue activity matrices for all appropriate sheets. 216 """ 217 for sheet in topo.sim.objects(Sheet).values(): 218 for c in ['Red','Green','Blue']: 219 # should this ensure all of r,g,b are present? 220 if hasattr(sheet,'activity_%s'%c.lower()): 221 activity_copy = getattr(sheet,'activity_%s'%c.lower()).copy() 222 new_view = SheetView((activity_copy,sheet.bounds), 223 sheet.name,sheet.precedence,topo.sim.time(),sheet.row_precedence) 224 sheet.sheet_views['%sActivity'%c]=new_view
225 226 227 pg = create_plotgroup(name='RGB',category='Other', 228 doc='Combine and plot the red, green, and blue activity for all appropriate Sheets.', auto_refresh=True, 229 pre_plot_hooks=[update_rgb_activities], plot_immediately=True) 230 pg.add_plot('RGB',[('Red','RedActivity'),('Green','GreenActivity'),('Blue','BlueActivity')]) 231 232 233
234 -class update_connectionfields(UnitMeasurementCommand):
235 """A callable Parameterized command for measuring or plotting a unit from a Projection.""" 236 237 # Force plotting of all CFs, not just one Projection 238 projection = param.ObjectSelector(default=None,constant=True)
239 240 241 pg= create_plotgroup(name='Connection Fields',category="Basic", 242 doc='Plot the weight strength in each ConnectionField of a specific unit of a Sheet.', 243 pre_plot_hooks=[update_connectionfields], 244 plot_immediately=True, normalize='Individually', situate=True) 245 pg.add_plot('Connection Fields',[('Strength','Weights')]) 246 247 248
249 -class update_projection(UnitMeasurementCommand):
250 """A callable Parameterized command for measuring or plotting units from a Projection."""
251 252 253 pg= create_plotgroup(name='Projection',category="Basic", 254 doc='Plot the weights of an array of ConnectionFields in a Projection.', 255 pre_plot_hooks=[update_projection], 256 plot_immediately=False, normalize='Individually',sheet_coords=True) 257 pg.add_plot('Projection',[('Strength','Weights')]) 258 259 260
261 -class update_projectionactivity(ProjectionSheetMeasurementCommand):
262 """ 263 Add SheetViews for all of the Projections of the ProjectionSheet 264 specified by the sheet parameter, for use in template-based plots. 265 """ 266
267 - def __call__(self,**params):
268 p=ParamOverrides(self,params) 269 self.params('sheet').compute_default() 270 s = p.sheet 271 if s is not None: 272 for conn in s.in_connections: 273 if not isinstance(conn,Projection): 274 topo.sim.debug("Skipping non-Projection "+conn.name) 275 else: 276 v = conn.get_projection_view(topo.sim.time()) 277 ###################################################################### 278 ## CEBALERT: when a TemplatePlot tp is created from a ProjectionView 279 ## pv, tp.plot_src_name comes ultimately from pv.projection.src.name, 280 ## but tp.plot_bounding_box comes from pv.projection.dest.bounds. So, 281 ## we get a plot with mismatched src_name and bounds. Fixing that here 282 ## is not the correct solution, but it allows the Projection Activity 283 ## GUI window to work. 284 v.src_name = v.projection.dest.name 285 ## JABALERT: Similarly, probably not the right thing to do, but makes 286 ## the name of the src available for the plot to be labelled. 287 v.proj_src_name = v.projection.src.name 288 ###################################################################### 289 key = ('ProjectionActivity',v.projection.dest.name,v.projection.name) 290 v.projection.dest.sheet_views[key] = v
291 292 293 pg = create_plotgroup(name='Projection Activity',category="Basic", 294 doc='Plot the activity in each Projection that connects to a Sheet.', 295 pre_plot_hooks=[update_projectionactivity.instance()], 296 plot_immediately=True, normalize='Individually',auto_refresh=True) 297 pg.add_plot('Projection Activity',[('Strength','ProjectionActivity')]) 298 299
300 -class measure_rfs(SingleInputResponseCommand):
301 """ 302 Map receptive fields by reverse correlation. 303 304 Presents a large collection of input patterns, typically pixel 305 by pixel on and off, keeping track of which units in the specified 306 input_sheet were active when each unit in other Sheets in the 307 simulation was active. This data can then be used to plot 308 receptive fields for each unit. Note that the results are true 309 receptive fields, not the connection fields usually presented in 310 lieu of receptive fields, because they take all circuitry in 311 between the input and the target unit into account. 312 313 Note also that it is crucial to set the scale parameter properly when 314 using units with a hard activation threshold (as opposed to a 315 smooth sigmoid), because the input pattern used here may not be a 316 very effective way to drive the unit to activate. The value 317 should be set high enough that the target units activate at least 318 some of the time there is a pattern on the input. 319 """ 320 static_parameters = param.List(default=["offset","size"]) 321 __abstract = True 322
323 - def __call__(self,**params):
324 p=ParamOverrides(self,params) 325 self.params('input_sheet').compute_default() 326 x=ReverseCorrelation(self._feature_list(p),input_sheet=p.input_sheet) 327 static_params = dict([(s,p[s]) for s in p.static_parameters]) 328 329 if p.duration is not None: 330 p.pattern_presenter.duration=p.duration 331 if p.apply_output_fns is not None: 332 p.pattern_presenter.apply_output_fns=p.apply_output_fns 333 x.collect_feature_responses(p.pattern_presenter,static_params,p.display,self._feature_list(p))
334
335 - def _feature_list(self,p):
336 337 # Obtain sheet dimensions and density. 338 left, bottom, right, top = p.input_sheet.nominal_bounds.lbrt() 339 sheet_density = float(p.input_sheet.nominal_density) 340 341 # Cannot assume square sheet so two independent values for axes divisions. 342 vertical_divisions = (sheet_density * (top - bottom)) - 1 343 horizontal_divisions = (sheet_density * (right - left)) - 1 344 345 # Calculate size of a division. 346 unit_size = 1.0 / sheet_density 347 half_unit_size = unit_size / 2.0 # saves repeated calculation. 348 p['size'] = unit_size 349 350 # Set the x and y max values down by half a unit so patterns are presented in the centre of each unit. 351 y_range = (top - half_unit_size, bottom) 352 x_range = (right - half_unit_size, left) 353 354 return [Feature(name="x", range=x_range, step=float(x_range[1]-x_range[0])/horizontal_divisions), 355 Feature(name="y", range=y_range, step=float(y_range[1]-y_range[0])/vertical_divisions), 356 Feature(name="scale", range=(-p.scale, p.scale), step=p.scale*2)]
357 358 pg = create_plotgroup(name='RF Projection',category='Other', 359 doc='Measure receptive fields.', 360 pre_plot_hooks=[measure_rfs.instance(display=True, 361 pattern_presenter=PatternPresenter(RawRectangle(size=0.01,aspect_ratio=1.0)))], 362 normalize='Individually') 363 364 pg.add_plot('RFs',[('Strength','RFs')]) 365 366 367 # Helper function for measuring direction maps
368 -def compute_orientation_from_direction(current_values):
369 """ 370 Return the orientation corresponding to the given direction. 371 372 Wraps the value to be in the range [0,pi), and rounds it slightly 373 so that wrapped values are precisely the same (to avoid biases 374 caused by vector averaging with keep_peak=True). 375 376 Note that in very rare cases (1 in 10^-13?), rounding could lead 377 to different values for a wrapped quantity, and thus give a 378 heavily biased orientation map. In that case, just choose a 379 different number of directions to test, to avoid that floating 380 point boundary. 381 """ 382 return round(((dict(current_values)['direction'])+(pi/2)) % pi,13)
383 384 385
386 -class measure_sine_pref(SinusoidalMeasureResponseCommand):
387 """ 388 Measure preferences for sine gratings in various combinations. 389 Can measure orientation, spatial frequency, spatial phase, 390 ocular dominance, horizontal phase disparity, color hue, motion 391 direction, and speed of motion. 392 393 In practice, this command is useful for any subset of the possible 394 combinations, but if all combinations are included, the number of 395 input patterns quickly grows quite large, much larger than the 396 typical number of patterns required for an entire simulation. 397 Thus typically this command will be used for the subset of 398 dimensions that need to be evaluated together, while simpler 399 special-purpose routines are provided below for other dimensions 400 (such as hue and disparity). 401 """ 402 403 num_ocularity = param.Integer(default=1,bounds=(1,None),softbounds=(1,3),doc=""" 404 Number of ocularity values to test; set to 1 to disable or 2 to enable.""") 405 406 num_disparity = param.Integer(default=1,bounds=(1,None),softbounds=(1,48),doc=""" 407 Number of disparity values to test; set to 1 to disable or e.g. 12 to enable.""") 408 409 num_hue = param.Integer(default=1,bounds=(1,None),softbounds=(1,48),doc=""" 410 Number of hues to test; set to 1 to disable or e.g. 8 to enable.""") 411 412 num_direction = param.Integer(default=0,bounds=(0,None),softbounds=(0,48),doc=""" 413 Number of directions to test. If nonzero, overrides num_orientation, 414 because the orientation is calculated to be perpendicular to the direction.""") 415 416 num_speeds = param.Integer(default=4,bounds=(0,None),softbounds=(0,10),doc=""" 417 Number of speeds to test (where zero means only static patterns). 418 Ignored when num_direction=0.""") 419 420 max_speed = param.Number(default=2.0/24.0,bounds=(0,None),doc=""" 421 The maximum speed to measure (with zero always the minimum).""") 422 423 subplot = param.String("Orientation") 424 425
426 - def _feature_list(self,p):
427 # Always varies frequency and phase; everything else depends on parameters. 428 429 features = \ 430 [Feature(name="frequency",values=p.frequencies)] 431 432 if p.num_direction==0: features += \ 433 [Feature(name="orientation",range=(0.0,pi),step=pi/p.num_orientation,cyclic=True)] 434 435 features += \ 436 [Feature(name="phase",range=(0.0,2*pi),step=2*pi/p.num_phase,cyclic=True)] 437 438 if p.num_ocularity>1: features += \ 439 [Feature(name="ocular",range=(0.0,1.0),step=1.0/p.num_ocularity)] 440 441 if p.num_disparity>1: features += \ 442 [Feature(name="phasedisparity",range=(0.0,2*pi),step=2*pi/p.num_disparity,cyclic=True)] 443 444 if p.num_hue>1: features += \ 445 [Feature(name="hue",range=(0.0,1.0),step=1.0/p.num_hue,cyclic=True)] 446 447 if p.num_direction>0 and p.num_speeds==0: features += \ 448 [Feature(name="speed",values=[0],cyclic=False)] 449 450 if p.num_direction>0 and p.num_speeds>0: features += \ 451 [Feature(name="speed",range=(0.0,p.max_speed),step=float(p.max_speed)/p.num_speeds,cyclic=False)] 452 453 if p.num_direction>0: 454 # Compute orientation from direction 455 dr = Feature(name="direction",range=(0.0,2*pi),step=2*pi/p.num_direction,cyclic=True) 456 or_values = list(set([compute_orientation_from_direction([("direction",v)]) for v in dr.values])) 457 features += [dr, \ 458 Feature(name="orientation",range=(0.0,pi),values=or_values,cyclic=True, 459 compute_fn=compute_orientation_from_direction)] 460 461 return features
462 463 464 # Here as the simplest possible example; could be moved elsewhere.
465 -class measure_or_pref(SinusoidalMeasureResponseCommand):
466 """Measure an orientation preference map by collating the response to patterns.""" 467 468 subplot = param.String("Orientation") 469
470 - def _feature_list(self,p):
471 return [Feature(name="frequency",values=p.frequencies), 472 Feature(name="orientation",range=(0.0,pi),step=pi/p.num_orientation,cyclic=True), 473 Feature(name="phase",range=(0.0,2*pi),step=2*pi/p.num_phase,cyclic=True)]
474 475 476 pg= create_plotgroup(name='Orientation Preference',category="Preference Maps", 477 doc='Measure preference for sine grating orientation.', 478 pre_plot_hooks=[measure_sine_pref.instance()]) 479 pg.add_plot('Orientation Preference',[('Hue','OrientationPreference')]) 480 pg.add_plot('Orientation Preference&Selectivity', 481 [('Hue','OrientationPreference'), ('Confidence','OrientationSelectivity')]) 482 pg.add_plot('Orientation Selectivity',[('Strength','OrientationSelectivity')]) 483 pg.add_plot('Phase Preference',[('Hue','PhasePreference')]) 484 pg.add_plot('Phase Selectivity',[('Strength','PhaseSelectivity')]) 485 pg.add_static_image('Color Key','command/or_key_white_vert_small.png') 486 487 488 pg= create_plotgroup(name='Spatial Frequency Preference',category="Preference Maps", 489 doc='Measure preference for sine grating orientation and frequency.', 490 pre_plot_hooks=[measure_sine_pref.instance()]) 491 pg.add_plot('Spatial Frequency Preference',[('Strength','FrequencyPreference')]) 492 pg.add_plot('Spatial Frequency Selectivity',[('Strength','FrequencySelectivity')]) 493 # Just calls measure_sine_pref to plot different maps. 494 495
496 -class measure_od_pref(SinusoidalMeasureResponseCommand):
497 """Measure an ocular dominance preference map by collating the response to patterns.""" 498
499 - def _feature_list(self,p):
500 return [Feature(name="frequency",values=p.frequencies), 501 Feature(name="orientation",range=(0.0,pi),step=pi/p.num_orientation,cyclic=True), 502 Feature(name="phase",range=(0.0,2*pi),step=2*pi/p.num_phase,cyclic=True), 503 Feature(name="ocular",range=(0.0,1.0),values=[0.0,1.0])]
504 505 pg= create_plotgroup(name='Ocular Preference',category="Preference Maps", 506 doc='Measure preference for sine gratings between two eyes.', 507 pre_plot_hooks=[measure_sine_pref.instance()]) 508 pg.add_plot('Ocular Preference',[('Strength','OcularPreference')]) 509 pg.add_plot('Ocular Selectivity',[('Strength','OcularSelectivity')]) 510 511 512 513
514 -class measure_phasedisparity(SinusoidalMeasureResponseCommand):
515 """Measure a phase disparity preference map by collating the response to patterns.""" 516 517 num_disparity = param.Integer(default=12,bounds=(1,None),softbounds=(1,48), 518 doc="Number of disparity values to test.") 519 520 orientation = param.Number(default=pi/2,softbounds=(0.0,2*pi),doc=""" 521 Orientation of the test pattern; typically vertical to measure 522 horizontal disparity.""") 523 524 static_parameters = param.List(default=["orientation","scale","offset"]) 525
526 - def _feature_list(self,p):
527 return [Feature(name="frequency",values=p.frequencies), 528 Feature(name="phase",range=(0.0,2*pi),step=2*pi/p.num_phase,cyclic=True), 529 Feature(name="phasedisparity",range=(0.0,2*pi),step=2*pi/p.num_disparity,cyclic=True)]
530 531 532 pg= create_plotgroup(name='PhaseDisparity Preference',category="Preference Maps",doc=""" 533 Measure preference for sine gratings at a specific orentation differing in phase 534 between two input sheets.""", 535 pre_plot_hooks=[measure_phasedisparity.instance()],normalize='Individually') 536 pg.add_plot('PhaseDisparity Preference',[('Hue','PhasedisparityPreference')]) 537 pg.add_plot('PhaseDisparity Preference&Selectivity', 538 [('Hue','PhasedisparityPreference'), ('Confidence','PhasedisparitySelectivity')]) 539 pg.add_plot('PhaseDisparity Selectivity',[('Strength','PhasedisparitySelectivity')]) 540 pg.add_static_image('Color Key','command/disp_key_white_vert_small.png') 541 542 543
544 -class measure_dr_pref(SinusoidalMeasureResponseCommand):
545 """Measure a direction preference map by collating the response to patterns.""" 546 547 num_phase = param.Integer(default=12) 548 549 num_direction = param.Integer(default=6,bounds=(1,None),softbounds=(1,48), 550 doc="Number of directions to test.") 551 552 num_speeds = param.Integer(default=4,bounds=(0,None),softbounds=(0,10),doc=""" 553 Number of speeds to test (where zero means only static patterns).""") 554 555 max_speed = param.Number(default=2.0/24.0,bounds=(0,None),doc=""" 556 The maximum speed to measure (with zero always the minimum).""") 557 558 subplot = param.String("Direction") 559
560 - def _feature_list(self,p):
561 # orientation is computed from direction 562 dr = Feature(name="direction",range=(0.0,2*pi),step=2*pi/p.num_direction,cyclic=True) 563 or_values = list(set([compute_orientation_from_direction([("direction",v)]) for v in dr.values])) 564 565 return [Feature(name="speed",values=[0],cyclic=False) if p.num_speeds is 0 else 566 Feature(name="speed",range=(0.0,p.max_speed),step=float(p.max_speed)/p.num_speeds,cyclic=False), 567 Feature(name="frequency",values=p.frequencies), 568 Feature(name="direction",range=(0.0,2*pi),step=2*pi/p.num_direction,cyclic=True), 569 Feature(name="phase",range=(0.0,2*pi),step=2*pi/p.num_phase,cyclic=True), 570 Feature(name="orientation",range=(0.0,pi),values=or_values,cyclic=True, 571 compute_fn=compute_orientation_from_direction)]
572 573 574 pg= create_plotgroup(name='Direction Preference',category="Preference Maps", 575 doc='Measure preference for sine grating movement direction.', 576 pre_plot_hooks=[measure_dr_pref.instance()]) 577 pg.add_plot('Direction Preference',[('Hue','DirectionPreference')]) 578 pg.add_plot('Direction Preference&Selectivity',[('Hue','DirectionPreference'), 579 ('Confidence','DirectionSelectivity')]) 580 pg.add_plot('Direction Selectivity',[('Strength','DirectionSelectivity')]) 581 pg.add_plot('Speed Preference',[('Strength','SpeedPreference')]) 582 pg.add_plot('Speed Selectivity',[('Strength','SpeedSelectivity')]) 583 pg.add_static_image('Color Key','command/dr_key_white_vert_small.png') 584 585 586
587 -class measure_hue_pref(SinusoidalMeasureResponseCommand):
588 """Measure a hue preference map by collating the response to patterns.""" 589 590 num_phase = param.Integer(default=12) 591 592 num_hue = param.Integer(default=8,bounds=(1,None),softbounds=(1,48), 593 doc="Number of hues to test.") 594 595 subplot = param.String("Hue") 596 597 # For backwards compatibility; not sure why it needs to differ from the default 598 static_parameters = param.List(default=[]) 599
600 - def _feature_list(self,p):
601 return [Feature(name="frequency",values=p.frequencies), 602 Feature(name="orientation",range=(0,pi),step=pi/p.num_orientation,cyclic=True), 603 Feature(name="hue",range=(0.0,1.0),step=1.0/p.num_hue,cyclic=True), 604 Feature(name="phase",range=(0.0,2*pi),step=2*pi/p.num_phase,cyclic=True)]
605 606 607 pg= create_plotgroup(name='Hue Preference',category="Preference Maps", 608 doc='Measure preference for colors.', 609 pre_plot_hooks=[measure_hue_pref.instance()],normalize='Individually') 610 pg.add_plot('Hue Preference',[('Hue','HuePreference')]) 611 pg.add_plot('Hue Preference&Selectivity',[('Hue','HuePreference'), ('Confidence','HueSelectivity')]) 612 pg.add_plot('Hue Selectivity',[('Strength','HueSelectivity')]) 613 614 615 616 617 gaussian_corner = topo.pattern.basic.Composite( 618 operator = maximum, generators = [ 619 topo.pattern.basic.Gaussian(size = 0.06,orientation=0,aspect_ratio=7,x=0.3), 620 topo.pattern.basic.Gaussian(size = 0.06,orientation=pi/2,aspect_ratio=7,y=0.3)]) 621 622
623 -class measure_corner_or_pref(PositionMeasurementCommand):
624 """Measure a corner preference map by collating the response to patterns.""" 625 626 scale = param.Number(default=1.0) 627 628 divisions=param.Integer(default=10) 629 630 pattern_presenter = param.Callable(PatternPresenter(gaussian_corner,apply_output_fns=False,duration=1.0)) 631 632 x_range=param.NumericTuple((-1.2,1.2)) 633 634 y_range=param.NumericTuple((-1.2,1.2)) 635 636 num_orientation = param.Integer(default=4,bounds=(1,None),softbounds=(1,24), 637 doc="Number of orientations to test.") 638 639 # JABALERT: Presumably this should be omitted, so that size is included? 640 static_parameters = param.List(default=["scale","offset"]) 641
642 - def _feature_list(self,p):
643 width =1.0*p.x_range[1]-p.x_range[0] 644 height=1.0*p.y_range[1]-p.y_range[0] 645 return [Feature(name="x",range=p.x_range,step=width/p.divisions), 646 Feature(name="y",range=p.y_range,step=height/p.divisions), 647 Feature(name="orientation",range=(0,2*pi),step=2*pi/p.num_orientation,cyclic=True)]
648 649 650 pg= create_plotgroup(name='Corner OR Preference',category="Preference Maps", 651 doc='Measure orientation preference for corner shape (or other complex stimuli that cannot be represented as fullfield patterns).', 652 pre_plot_hooks=[measure_corner_or_pref.instance()], 653 normalize='Individually') 654 pg.add_plot('Corner Orientation Preference',[('Hue','OrientationPreference')]) 655 pg.add_plot('Corner Orientation Preference&Selectivity',[('Hue','OrientationPreference'), 656 ('Confidence','OrientationSelectivity')]) 657 pg.add_plot('Corner Orientation Selectivity',[('Strength','OrientationSelectivity')]) 658 659
660 -class measure_corner_angle_pref(PositionMeasurementCommand):
661 """Generate the preference map for angle shapes, by collating the response to patterns.""" 662 663 scale = param.Number(default=1.0) 664 665 size = param.Number(default=0.2) 666 667 positions = param.Integer(default=6) 668 669 x_range = param.NumericTuple((-1.0, 1.0)) 670 671 y_range = param.NumericTuple((-1.0, 1.0)) 672 673 num_or = param.Integer(default=4,bounds=(1,None),softbounds=(1,24),doc= 674 "Number of orientations to test.") 675 676 angle_0 = param.Number(default=0.25*pi,bounds=(0.0,pi),softbounds=(0.0,0.5*pi),doc= 677 "First angle to test.") 678 679 angle_1 = param.Number(default=0.75*pi,bounds=(0.0,pi),softbounds=(0.5*pi,pi),doc= 680 "Last angle to test.") 681 682 num_angle=param.Integer(default=4,bounds=(1,None),softbounds=(1,12),doc= 683 "Number of angles to test.") 684 685 key_img_fname=param.Filename(default='command/key_angles.png',doc= 686 "Name of the file with the image used to code angles with hues.") 687 688 pattern_presenter=PatternPresenter(GaussiansCorner(aspect_ratio=4.0,cross=0.85),apply_output_fns=False,duration=1.0) 689 690 static_parameters = param.List( default=[ "size", "scale", "offset" ] ) 691 692 693
694 - def _feature_list( self, p ):
695 """Return the list of features to vary, generate hue code static image""" 696 x_step = ( p.x_range[1]-p.x_range[0] ) / float( p.positions - 1 ) 697 y_step = ( p.y_range[1]-p.y_range[0] ) / float( p.positions - 1 ) 698 o_step = 2.0*pi / p.num_or 699 if p.angle_0 < p.angle_1: 700 angle_0 = p.angle_0 701 angle_1 = p.angle_1 702 else: 703 angle_0 = p.angle_1 704 angle_1 = p.angle_0 705 a_range = ( angle_0, angle_1 ) 706 a_step = ( angle_1 - angle_0 ) / float( p.num_angle - 1 ) 707 self._make_key_image( p ) 708 return [ 709 Feature( name = "x", range = p.x_range, step = x_step ), 710 Feature( name = "y", range = p.y_range, step = y_step ), 711 Feature( name = "orientation", range = (0, 2*pi), step = o_step, cyclic = True ), 712 Feature( name = "angle", range = a_range, step = a_step, 713 value_offset = - angle_0, 714 value_multiplier = 1. / ( angle_1 - angle_0 ) ) 715 ]
716 717
718 - def _make_key_image( self, p ):
719 """Generate the image with keys to hues used to code angles 720 the image is saved on-the-fly, in order to fit the current 721 choice of angle range 722 """ 723 width = 60 724 height = 300 725 border = 6 726 n_a = 7 727 angle_0 = p.angle_0 728 angle_1 = p.angle_1 729 a_step = 0.5 * ( angle_1 - angle_0 ) / float( n_a ) 730 x_0 = border 731 x_1 = ( width - border ) / 2 732 x_a = x_1 + 2 * border 733 y_use = height - 2 * border 734 y_step = y_use / float( n_a ) 735 y_d = int( float( 0.5 * y_step ) ) 736 y_0 = border + y_d 737 l = 15 738 739 hues = [ "hsl(%2d,100%%,50%%)" % h for h in range( 0, 255, 255 / n_a) ] 740 angles = [ 0.5*angle_0 + a_step * a for a in range( n_a ) ] 741 y_pos = [ int( round( y_0 + y * y_step ) ) for y in range( n_a ) ] 742 deltas = [ ( int( round( l * cos( a ) ) ), int( round( l * sin( a ) ) ) ) 743 for a in angles ] 744 lb_img = Image.new( "RGB", ( width, height ), "white" ) 745 dr_img = ImageDraw.Draw( lb_img ) 746 747 for h, y, d in zip( hues, y_pos, deltas ): 748 dr_img.rectangle( [ ( x_0, y - y_d ), ( x_1, y + y_d ) ], fill = h ) 749 dr_img.line( [ ( x_a, y ), ( x_a + d[ 0 ], y + d[ 1 ] ) ], fill = "black" ) 750 dr_img.line( [ ( x_a, y ), ( x_a + d[ 0 ], y - d[ 1 ] ) ], fill = "black" ) 751 752 lb_img.save( p.key_img_fname )
753 754 #return( p.key_img_fname.default ) 755 756 757 pg= create_plotgroup(name='Corner Angle Preference',category="Preference Maps", 758 doc='Measure preference for angles in corner shapes', 759 normalize='Individually') 760 pg.pre_plot_hooks=[ measure_corner_angle_pref.instance() ] 761 pg.add_plot('Corner Angle Preference',[('Hue','AnglePreference')]) 762 pg.add_plot('Corner Angle Preference&Selectivity',[('Hue','AnglePreference'), 763 ('Confidence','AngleSelectivity')]) 764 pg.add_plot('Corner Angle Selectivity',[('Strength','AngleSelectivity')]) 765 pg.add_plot('Corner Orientation Preference',[('Hue','OrientationPreference')]) 766 pg.add_plot('Corner Orientation Preference&Selectivity',[('Hue','OrientationPreference'), 767 ('Confidence','OrientationSelectivity')]) 768 pg.add_plot('Corner Orientation Selectivity',[('Strength','OrientationSelectivity')]) 769 pg.add_static_image( 'Hue Code', measure_corner_angle_pref.instance().key_img_fname ) 770 771 772 # Measure sound frequency preference maps
773 -class measure_frequency_pref(PositionMeasurementCommand):
774 """Measure a frequency preference and selectivity map""" 775 776 display = param.Boolean(True) 777 pattern_presenter = param.Callable(PatternPresenter(Line(smoothing=0.0001,thickness=0.05))) 778 779 # BK-ALERT: These are hard coded to the lissom audio sheet dimensions. 780 # i'm not sure how to avoid that, PositionMeasurementCommand isn't 781 # actually able to access the sheet dimensions. 782 y_range = param.NumericTuple((-0.5,0.5)) 783 divisions = param.Integer(100) 784
785 - def _feature_list(self,p):
786 return [Feature(name="x", values=[0.0]), 787 Feature(name="y", range=p.y_range, step=(p.y_range[1]-p.y_range[0])/float(p.divisions))]
788 789 790 pg= create_plotgroup(name='Frequency Preference and Selectivity',category="Preference Maps", 791 pre_plot_hooks=[measure_frequency_pref.instance()], normalize='Individually', 792 doc='Measure best frequency preference and selectivity for auditory neurons.') 793 794 pg.add_plot('[Frequency Preference]', [('Strength','YPreference')]) 795 pg.add_plot('[Frequency Selectivity]', [('Strength','YSelectivity')]) 796 797 798 import types 799 __all__ = list(set([k for k,v in locals().items() 800 if isinstance(v,types.FunctionType) or 801 (isinstance(v,type) and issubclass(v,ParameterizedFunction)) 802 and not v.__name__.startswith('_')])) 803