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
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
71
72
73
74
75 self.distribution_matrix + fromfunction(vectorize(lambda i,j: {bin:new_values[i,j]}),
76 new_values.shape)
77
78
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
88
89
90 return weighted_average_matrix
91
92
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
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
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
147
148
149
150
151
152
153
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
175
176
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
196
208
213
254
288
295
321
322
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
353
379
380
387
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
407
408
409
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
417
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
452
453
454
455
456
457
458
459
460
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
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):
525
528
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.