1 """
2 Hierarchy of PlotGroup classes, i.e. output-device-independent sets of
3 plots.
4
5 Includes PlotGroups for standard plots of anything in a Sheet
6 database, plus weight plots for one unit, and projections.
7
8 $Id: plotgroup.py 11315 2010-07-27 17:43:01Z ceball $
9 """
10 __version__='$Revision: 11315 $'
11
12 import copy
13 import Image
14
15 import param
16 from param.parameterized import ParamOverrides
17 from param import resolve_path
18
19 import topo
20
21 from topo.base.cf import CFSheet,CFProjection
22 from topo.base.projection import ProjectionSheet
23 from topo.sheet import GeneratorSheet,Sheet
24 from topo.misc.keyedlist import KeyedList
25
26 from plot import make_template_plot, Plot
27 from plotfilesaver import PlotGroupSaver,CFProjectionPlotGroupSaver
28
29
30
31
32
33
34
35
36
37
38
39
41 """
42 Comparison function for sorting Plots.
43 It compares the precedence number first and then the src_name and name attributes.
44 """
45 if plot1.precedence != plot2.precedence:
46 return cmp(plot1.precedence,plot2.precedence)
47 else:
48 return cmp((plot1.plot_src_name+plot1.name),
49 (plot2.plot_src_name+plot2.name))
50
51
53 """
54 Container that has one or more Plots and also knows how to arrange
55 the plots and other special parameters.
56 """
57
58 pre_plot_hooks = param.HookList(default=[],doc="""
59 Commands to execute before updating this plot, e.g. to calculate sheet views.
60
61 The commands can be any callable Python objects, i.e. any x for
62 which x() is valid. The initial value is determined by the
63 template for this plot, but various arguments can be passed, a
64 modified version substituted, etc.""")
65
66 plot_hooks = param.HookList(default=[],doc="""
67 Commands to execute when redrawing a plot rather than regenerating data.
68
69 E.g, for a plot with data measured once but displayed one
70 sheet or unit at at time, this command will be called whenever
71 the sheet or coordinate of unit to be plotted (or the
72 simulator time) has changed.
73
74 The commands can be any callable Python objects, i.e. any x for
75 which x() is valid. The initial value is determined by the
76 template for this plot, but various arguments can be passed, a
77 modified version substituted, etc.""")
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
106
108 """Scale the images by the given zoom factor, if appropriate; default is to do nothing."""
109 pass
110
111
112
113
114
116 """Return the list of Plots"""
117
118 return self._static_plots[:]
119
120
123
124
126 """Sort plots according to their precedence, then alphabetically."""
127 self.plots.sort(_cmp_plot)
128
129
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
159
160
164
165
166
168 """
169 Generate the sorted and scaled list of plots constituting the PlotGroup.
170 """
171 self.plots = [plot for plot in self._generate_plots() if plot is not None]
172
173
174
175
176 resizeable_plots = [plot for plot in self.plots if plot.resize]
177 if not update and not resizeable_plots:
178 self.plots=[]
179
180
181 timestamps = [plot.timestamp for plot in self.plots
182 if plot.timestamp >= 0]
183 if len(timestamps)>0:
184 self.time = max(timestamps)
185 if max(timestamps) != min(timestamps):
186 self.warning("Combining Plots from different times (%s,%s)" %
187 (min(timestamps),max(timestamps)))
188
189 self._sort_plots()
190 self.labels = self._generate_labels()
191
192
193
194
195
196
197
198
199
200
202
203
204
205
206
207
208
209 sheet_coords = param.Boolean(default=False,doc="""
210 Whether to scale plots based on their relative sizes in sheet
211 coordinates. If true, plots are scaled so that their sizes are
212 proportional to their area in sheet coordinates, so that one can
213 compare corresponding areas. If false, plots are scaled to have
214 similar sizes on screen, regardless of their corresponding
215 sheet areas, which maximizes the size of each plot.""")
216
217
218
219
220
221 normalize = param.ObjectSelector(default='None',
222 objects=['None','Individually'],
223 doc="""
224 'Individually': scale each plot so that the peak value will be white
225 and the minimum value black.
226
227 'None': no scaling - 0.0 will be black and 1.0 will be white.
228
229 Normalization has the advantage of ensuring that any data that
230 is present will be visible, but the disadvantage that the
231 absolute scale will be obscured. Non-normalized plots are
232 guaranteed to be on a known scale, but only values between 0.0
233 and 1.0 will be visibly distinguishable.""")
234
235 integer_scaling = param.Boolean(default=False,doc="""
236 When scaling bitmaps, whether to ensure that the scaled bitmap is an even
237 multiple of the original. If true, every unit will be represented by a
238 square of the same size. Typically false so that the overall area will
239 be correct, e.g. when using Sheet coordinates, which is often more
240 important.""")
241
242 auto_refresh = param.Boolean(default=False,doc="""
243 If this plot is being displayed persistently (e.g. in a GUI),
244 whether to regenerate it automatically whenever the simulation
245 time advances. The default is False, because many plots are
246 slow to generate (including most preference map plots).""")
247
248 desired_maximum_plot_height = param.Number(default=0,bounds=(0,None),doc="""
249 User-specified height of the tallest plot in this PlotGroup.
250 Other plots will generally be scaled as appropriate, either
251 to match this size (when sheet_coords is False), or to
252 have the appropriate relative size (when sheet_coords is True).
253
254 If enforce_minimum_plot_height is True, the actual maximum
255 plot height may be larger than this parameter's value. In
256 particular, with enforce_minimum_plot_height=True, the default
257 value of 0 gives plots that are the size of the underlying
258 matrix, which is the most efficient size for saving plots
259 directly to disk. Larger values (e.g. 150) are suitable when
260 displaying plots on screen.""")
261
262 enforce_minimum_plot_height = param.Boolean(default=True,doc="""
263 If true, ensure that plots are never shown smaller than their
264 native size, i.e. with fewer than one pixel per matrix unit.
265 This option is normally left on for safety, so that no
266 visualization will be missing any units. However, it may be
267 acceptable to turn this check off when working with matrix
268 sizes much larger than your screen resolution.""")
269
270
271
272
273
274
275
276
277
280
281
283 """
284 Determine which plot will be the largest, based on the
285 settings for minimum plot heights, the specified zoom factor
286 (if not None), etc.
287
288 A minimum size (and potentially a maximum size) are enforced,
289 as described below.
290
291 If the scaled sizes would be outside of the allowed range,
292 False is returned.
293
294 For matrix coordinate plots (sheet_coords=False), the minimum
295 size is calculated as the native size of the largest bitmap to
296 be plotted. Other plots are then usually scaled up to (but
297 not greater than) this size, so that all plots are
298 approximately the same size, and no plot is missing any pixel.
299
300 For Sheet coordinate plots, the minimum plotting density that
301 will avoid losing pixels is determined by the maximum density
302 from any sheet. If all plots are then drawn at that density
303 (as they must be for them to be in Sheet coordinates), the
304 largest plot will then be the one with the largest sheet
305 bounds, and the size of that plot will be the maximum density
306 times the largest sheet bounds.
307 """
308
309 if zoom_factor:
310 self.desired_maximum_plot_height*=zoom_factor
311
312 self.maximum_plot_height=self.desired_maximum_plot_height
313
314 if (self.enforce_minimum_plot_height):
315 resizeable_plots = [p for p in self.plots if p.resize]
316
317
318 if not resizeable_plots:
319 return False
320
321 if not self.sheet_coords:
322 bitmap_heights = [p._orig_bitmap.height() for p in resizeable_plots]
323 minimum_height_of_tallest_plot = max(bitmap_heights)
324
325 else:
326
327
328
329
330
331 sheets = topo.sim.objects(Sheet)
332 max_sheet_height = max([(sheets[p.plot_src_name].bounds.lbrt()[3]-
333 sheets[p.plot_src_name].bounds.lbrt()[1])
334 for p in resizeable_plots])
335 max_sheet_density = max([sheets[p.plot_src_name].xdensity
336 for p in resizeable_plots])
337 minimum_height_of_tallest_plot = max_sheet_height*max_sheet_density
338 self.max_sheet_height=max_sheet_height
339
340 if (self.maximum_plot_height < minimum_height_of_tallest_plot):
341 self.maximum_plot_height = minimum_height_of_tallest_plot
342
343 if zoom_factor:
344 return zoom_factor>1.0
345
346
347 return True
348
349
350
351
352
353
354
355
357 """
358 Enlarge or reduce the bitmaps as needed for display.
359
360 The calculated sizes will be multiplied by the given
361 zoom_factor, if it is not None.
362
363 If the scaled sizes would be outside of the allowed range, no
364 scaling is done, and False is returned. (One might
365 conceivably instead want the scaling to reach the actual
366 minimum or maximum allowed, but if we did this, then repeated
367 enlargements and reductions would not be reversible, unless we
368 were very tricky about how we did it.)
369 """
370
371
372 resizeable_plots = [p for p in self.plots if p.resize]
373 if not resizeable_plots or not self.desired_maximum_plot_height:
374 return False
375
376
377 if not self.update_maximum_plot_height(zoom_factor) and zoom_factor:
378 return False
379
380
381 for plot in resizeable_plots:
382 if self.sheet_coords:
383 s = topo.sim.objects(Sheet)[plot.plot_src_name]
384 scaling_factor=self.maximum_plot_height/float(s.xdensity)/self.max_sheet_height
385 else:
386 scaling_factor=self.maximum_plot_height/float(plot._orig_bitmap.height())
387
388 if self.integer_scaling:
389 scaling_factor=max(1,int(scaling_factor))
390
391 plot.set_scale(scaling_factor)
392
393
394
395 return True
396
397
398
400 """
401 Helper function to return the (min,max) given by the value_ranges of
402 the given plots.
403
404 Return None if there were no plots with value_range.
405 """
406 mins = []
407 maxs = []
408 for plot in plots:
409 if hasattr(plot,'value_range'):
410 mins.append(plot.value_range[0])
411 maxs.append(plot.value_range[1])
412
413 if len(mins)==0:
414 return None
415 else:
416 return (min(mins),max(maxs))
417
418
420 """
421 Container that allows creation of different types of plots in a
422 way that is independent of particular models or Sheets.
423
424 A TemplatePlotGroup is constructed from a plot_templates list, an
425 optional command to run to generate the data, and other optional
426 parameters.
427
428 The plot_templates list should contain tuples (plot_name,
429 plot_template). Each plot_template is a list of (name, value)
430 pairs, where each name specifies a plotting channel (such as Hue
431 or Confidence), and the value is the name of a SheetView (such as
432 Activity or OrientationPreference).
433
434 Various types of plots support different channels. An SHC
435 plot supports Strength, Hue, and Confidence channels (with
436 Strength usually being visualized as luminance, Hue as a color
437 value, and Confidence as the saturation of the color). An RGB
438 plot supports Red, Green, and Blue channels. Other plot types
439 will be added eventually.
440
441 For instance, one could define an Orientation-colored Activity
442 plot as::
443
444 plotgroups['Activity'] =
445 TemplatePlotGroup(name='Activity', category='Basic',
446 pre_plot_hooks=[measure_activity],
447 plot_templates=[('Activity',
448 {'Strength': 'Activity', 'Hue': 'OrientationPreference', 'Confidence': None})])
449
450 This specifies that the final TemplatePlotGroup will contain up to
451 one Plot named Activity per Sheet, although there could be no
452 plots at all if no Sheet has a SheetView named Activity once
453 'measure_activity()' has been run. The Plot will be colored by
454 OrientationPreference if such a SheetView exists for that Sheet,
455 and the value (luminance) channel will be determined by the
456 SheetView Activity. This plot will be listed in the category
457 'Basic' anywhere such categories are relevant (e.g. in the GUI).
458
459
460 Here's a more complicated example specifying two different plots
461 in the same PlotGroup::
462
463 TemplatePlotGroup(name='Orientation Preference', category='Basic'
464 pre_plot_hooks=[measure_or_pref.instance()],
465 plot_templates=
466 [('Orientation Preference',
467 {'Strength': None, 'Hue': 'OrientationPreference'}),
468 ('Orientation Selectivity',
469 {'Strength': 'OrientationSelectivity'})])
470
471 Here the TemplatePlotGroup will contain up to two Plots per Sheet,
472 depending on which Sheets have OrientationPreference and
473 OrientationSelectivity SheetViews.
474
475
476 The function create_plotgroup provides a convenient way to define plots using
477 TemplatePlotGroups; search for create_plotgroup elsewhere in the code to see
478 examples.
479 """
480
481 doc = param.String(default="",doc="""
482 Documentation string describing this type of plot.""")
483
484 plot_immediately=param.Boolean(False,doc="""
485 Whether to call the pre-plot hooks at once or only when the user asks for a refresh.
486
487 Should be set to true for quick plots, but false for those that take a long time
488 to calculate, so that the user can change the hooks if necessary.""")
489
490 prerequisites=param.List([],doc="""
491 List of preference maps that must exist before this plot can be calculated.""")
492
493 category = param.String(default="User",doc="""
494 Category to which this plot belongs, which will be created if necessary.""")
495
496
497
498 normalize = param.ObjectSelector(default='None',
499 objects=['None','Individually','AllTogether'],doc="""
500
501 'Individually': scale each plot so that its maximum value is
502 white and its minimum value black.
503
504 'None': no scaling (0.0 will be black and 1.0 will be white).
505
506 'AllTogether': scale each plot so that the highest maximum value is
507 white, and the lowest minimum value is black.
508
509
510 Normalizing 'Individually' has the advantage of ensuring that
511 any data that is present will be visible, but the disadvantage
512 that the absolute scale will be obscured. Non-normalized
513 plots are guaranteed to be on a known scale, but only values
514 between 0.0 and 1.0 will be visibly distinguishable.""")
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
540
541
542 - def __init__(self,plot_templates=None,static_images=None,**params):
548
549
550
551
552
553
554
555
556
557
558
559
560
561
563 dict_={}
564 for key,value in specification_tuple_list:
565 dict_[key]=value
566 self.plot_templates.append((name,dict_))
567
568 add_plot = add_template
569
570
578
579
580
581
582
583
592
593
604
605
615
616
617
631
632
633
635 """Abstract PlotGroup for visualizations of the Projections of one ProjectionSheet."""
636 __abstract = True
637
638
639
640 keyname = "ProjectionSheet"
641
642 sheet = param.ObjectSelector(default=None,
643 compute_default_fn=default_measureable_sheet,
644 doc=
645 """The Sheet from which to produce plots.""")
646
647 normalize = param.ObjectSelector(default='None',
648 objects=['None','Individually','AllTogether','JointProjections'],doc="""
649 'Individually': scale each plot so that the peak value will be white
650 and the minimum value black.
651
652 'None': no scaling - 0.0 will be black and 1.0 will be white.
653
654 'AllTogether': scale each plot so that the peak value of all the plots
655 is white, and the minimum value of all the plots will be
656 black.
657
658 'JointProjections': as 'Individually', except that plots produced from
659 projections whose weights are jointly normalized will be
660 jointly normalized.
661
662 Normalization has the advantage of ensuring that any data that
663 is present will be visible, but the disadvantage that the
664 absolute scale will be obscured. Non-normalized plots are
665 guaranteed to be on a known scale, but only values between 0.0
666 and 1.0 will be visibly distinguishable.""")
667
668
669 sheet_type = ProjectionSheet
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
694
695
696
697
698
701
706
707
710
711
719
720
751
752
754 return make_template_plot(self._channels(plot_template,**kw),
755 kw['proj'].src.sheet_views,
756 kw['proj'].src.xdensity,
757 None,
758 self.normalize,
759 name=kw['proj'].name,
760 range_=kw['range_'])
761
762
764 return ["%s%s"%(plot.name,
765 (("\n(from %s)" % plot.proj_src_name)
766 if hasattr(plot,"proj_src_name") else ""))
767 for plot in self.plots]
768
769
770
771
772
773 - def _key(self,**kw):
776
777
779
780
781
782 return [dict(proj=proj)]
783
784
785
786