1 """
2 Line-based and matrix-based plotting commands using MatPlotLib.
3
4 Before importing this file, you will probably want to do something
5 like:
6
7 from matplotlib import rcParams
8 rcParams['backend']='TkAgg'
9
10 to select a backend, or else select an appropriate one in your
11 matplotlib.rc file (if any). There are many backends available for
12 different GUI or non-GUI uses.
13
14 $Id: pylabplot.py 11307 2010-07-27 16:40:49Z ceball $
15 """
16 __version__='$Revision: 11307 $'
17
18 import param
19
20 try:
21 import matplotlib.ticker
22 import pylab
23 except ImportError:
24 param.Parameterized(name=__name__).warning("Could not import matplotlib; module will not be useable.")
25 from basic import ImportErrorRaisingFakeModule
26 pylab = ImportErrorRaisingFakeModule("matplotlib")
27
28
29 import numpy
30 from math import pi
31
32 from numpy.oldnumeric import sqrt, array, transpose, argmin, cos, sin, log10, Float
33 from numpy import outer,arange,ones,zeros
34
35 from numpy.fft.fftpack import fft2
36 from numpy.fft.helper import fftshift
37 from numpy import abs
38
39 import topo
40 from topo.base.sheetview import SheetView
41 from topo.base.arrayutil import centroid, wrap
42 from topo.base.sheet import Sheet
43 from topo.misc.util import frange
44 import topo.analysis.vision
45 from topo.plotting.plot import make_template_plot
46 import param
47 from param import ParameterizedFunction,normalize_path
48 from param.parameterized import ParamOverrides
49 from topo.pattern.basic import SineGrating, OrientationContrast
50 from topo.plotting.plotgroup import create_plotgroup
51 from topo.base.cf import CFSheet
52
53 from topo.analysis.featureresponses import Feature, PatternPresenter
54 from topo.analysis.featureresponses import PositionMeasurementCommand, FeatureCurveCommand, UnitCurveCommand
55
56
57 from basic import Command
58
59
60
61
62
64 """Parameterized command for plotting using Matplotlib/Pylab."""
65
66 file_dpi = param.Number(
67 default=100.0,bounds=(0,None),softbounds=(0,1000),doc="""
68 Default DPI when rendering to a bitmap.
69 The nominal size * the dpi gives the final image size in pixels.
70 E.g.: 4"x4" image * 80 dpi ==> 320x320 pixel image.""")
71
72 file_format = param.String(default="png",doc="""
73 Which image format to use when saving images.
74 The output can be png, ps, pdf, svg, or any other format
75 supported by Matplotlib.""")
76
77
78
79
80
81
82 filename = param.String(default=None,doc="""
83 Optional base of the filename to use when saving images;
84 if None the plot will be displayed interactively.
85
86 The actual name is constructed from the filename base plus the
87 suffix plus the current simulator time plus the file_format.""")
88
89 filename_suffix = param.String(default="",doc="""
90 Optional suffix to be used for disambiguation of the filename.""")
91
92 title = param.String(default=None,doc="""
93 Optional title to be used when displaying the plot interactively.""")
94
95 __abstract = True
96
97
99 """
100 Helper function to set the title (if not None) of this PyLab plot window.
101 """
102
103
104
105
106
107
108
109
110 if title is not None:
111 try:
112 manager = pylab.get_current_fig_manager()
113 manager.window.title(title)
114 except:
115 pass
116
117
134
135
136
138 """
139 Simple line plotting for any vector or list of numbers.
140
141 Intended for interactive debugging or analyzing from the command
142 prompt. See MatPlotLib's pylab functions to create more elaborate
143 or customized plots; this is just a simple example.
144
145 An optional string can be supplied as a title for the figure, if
146 desired. At present, this is only used for the window, not the
147 actual body of the figure (and will thus not appear when the
148 figure is saved).
149
150 The style argument allows different line/linespoints style for
151 the plot: 'r-' for red solid line, 'bx' for blue x-marks, etc.
152 See http://matplotlib.sourceforge.net/matplotlib.pylab.html#-plot
153 for more possibilities.
154
155 The label argument can be used to identify the line in a figure legend.
156
157 Ordinarily, the x value for each point on the line is the index of
158 that point in the vec array, but a explicit list of xvalues can be
159 supplied; it should be the same length as vec.
160
161 Execution of multiple vectorplot() commands with different styles
162 will result in all those styles overlaid on a single plot window.
163 """
164
165
166 - def __call__(self,vec,xvalues=None,style='-',label=None,**params):
176
177
178
180 """
181 Simple plotting for any matrix as a bitmap with axes.
182
183 Like MatLab's imagesc, scales the values to fit in the range 0 to 1.0.
184 Intended for interactive debugging or analyzing from the command
185 prompt. See MatPlotLib's pylab functions to create more elaborate
186 or customized plots; this is just a simple example.
187 """
188
189 plot_type = param.Callable(default=pylab.gray,doc="""
190 Matplotlib command to generate the plot, e.g. pylab.gray or pylab.hsv.""")
191
192
193 - def __call__(self,mat,aspect=None,colorbar=True,**params):
201
202
204 """
205 Simple plotting for any matrix as a 3D wireframe with axes.
206
207 Uses Matplotlib's beta-quality features for 3D plotting. These
208 usually work fine for wireframe plots, although they don't always
209 format the axis labels properly, and do not support removal of
210 hidden lines. Note that often the plot can be rotated within the
211 window to make such problems go away, and then the best result can
212 be saved if needed.
213
214 Other than the default "wireframe", the type can be "contour" to
215 get a contour plot, or "surface" to get a solid surface plot, but
216 surface plots currently fail in many cases, e.g. for small
217 matrices.
218
219 If you have trouble, you can try matrixplot3d_gnuplot instead.
220 """
221
222
223 - def __call__(self,mat,type="wireframe",**params):
224 p=ParamOverrides(self,params)
225
226 from mpl_toolkits.mplot3d import axes3d
227
228 fig = pylab.figure()
229 ax = axes3d.Axes3D(fig)
230
231
232 rn,cn = mat.shape
233 c = outer(ones(rn),arange(cn*1.0))
234 r = outer(arange(rn*1.0),ones(cn))
235
236 if type=="wireframe":
237 ax.plot_wireframe(r,c,mat)
238 elif type=="surface":
239
240 ax.plot_surface(r,c,mat)
241 elif type=="contour":
242
243 ax.contour3D(r,c,mat)
244 else:
245 raise ValueError("Unknown plot type "+str(type))
246
247 ax.set_xlabel('R')
248 ax.set_ylabel('C')
249 ax.set_zlabel('Value')
250
251 self._generate_figure(p)
252
253
254
256 """
257 Simple plotting for any matrix as a 3D surface with axes.
258
259 Currently requires the gnuplot-py package to be installed, plus
260 the external gnuplot program; likely to be removed once Matplotlib
261 supports 3D plots better.
262
263 Unlikely to work on non-UNIX systems.
264
265 Should return when it completes, but for some reason the Topographica
266 prompt is not available until this command finishes.
267 """
268 import Gnuplot
269 from os import system
270
271 psviewer="gv"
272 g = Gnuplot.Gnuplot(debug=0)
273 r,c = mat.shape
274 x = arange(r*1.0)
275 y = arange(c*1.0)
276
277
278 m = numpy.asarray(mat,dtype="float32").tolist()
279
280 g("set data style lines")
281 g("set hidden3d")
282 g("set xlabel 'R'")
283 g("set ylabel 'C'")
284 g("set zlabel 'Value'")
285 if title: g.title(title)
286
287 if outputfilename:
288 g("set terminal postscript eps color solid 'Times-Roman' 14")
289 g("set output '"+outputfilename+"'")
290 g.splot(Gnuplot.GridData(m,x,y, binary=1))
291
292 system(psviewer+" "+outputfilename+" &")
293
294 else:
295 g.splot(Gnuplot.GridData(m,x,y, binary=1))
296 raw_input('Please press return to continue...\n')
297
298
299
301 """
302 Compute and plot the histogram of the supplied data.
303
304 See help(pylab.hist) for help on the histogram function itself.
305
306 If given, colors is an iterable collection of matplotlib.colors
307 (see help (matplotlib.colors) ) specifying the bar colors.
308
309 Example use:
310 histogramplot([1,1,1,2,2,3,4,5],title='hist',colors='rgb',bins=3,normed=1)
311 """
312
313
314 - def __call__(self,data,colors=None,**params):
325
326
327
329 """
330 Compute and show the gradient plot of the supplied data.
331 Translated from Octave code originally written by Yoonsuck Choe.
332
333 If the data is specified to be cyclic, negative differences will
334 be wrapped into the range specified (1.0 by default).
335 """
336
337
338 - def __call__(self,data,cyclic=True,cyclic_range=1.0,**params):
339 p=ParamOverrides(self,params)
340
341 r,c = data.shape
342 dx = numpy.diff(data,1,axis=1)[0:r-1,0:c-1]
343 dy = numpy.diff(data,1,axis=0)[0:r-1,0:c-1]
344
345 if cyclic:
346
347 dx = wrap(0,cyclic_range,dx)
348 dy = wrap(0,cyclic_range,dy)
349
350
351
352 dx = 0.5*cyclic_range-abs(dx-0.5*cyclic_range)
353 dy = 0.5*cyclic_range-abs(dy-0.5*cyclic_range)
354
355 super(gradientplot,self).__call__(sqrt(dx*dx+dy*dy),**p)
356
357
358
360 """
361 Compute and show the 2D Fast Fourier Transform (FFT) of the supplied data.
362
363 Example:: fftplot(topo.sim["V1"].sheet_views["OrientationPreference"].view()[0],filename="out")
364 """
365
370
371
372
374 """
375 Plots the activity in a sheet.
376
377 Gets plot's extent from sheet.bounds.aarect(). Adds a title and
378 allows the selection of a colormap. If activity is not given,
379 the sheet's current activity is used.
380 """
381
382
383
384 - def __call__(self,sheet,activity=None,cmap=None,**params):
395
396
397
399 """
400 By default, plot the XPreference and YPreference preferences for all
401 Sheets for which they are defined, using MatPlotLib.
402
403 If sheet_views other than XPreference and YPreference are desired,
404 the names of these can be passed in as arguments.
405 """
406
407 xsheet_view_name = param.String(default='XPreference',doc="""
408 Name of the SheetView holding the X position locations.""")
409
410 ysheet_view_name = param.String(default='YPreference',doc="""
411 Name of the SheetView holding the Y position locations.""")
412
413 axis = param.Parameter(default=[-0.5,0.5,-0.5,0.5],doc="""
414 Four-element list of the plot bounds, i.e. [xmin, xmax, ymin, ymax].""")
415
417 p=ParamOverrides(self,params)
418
419 for sheet in topo.sim.objects(Sheet).values():
420 if ((p.xsheet_view_name in sheet.sheet_views) and
421 (p.ysheet_view_name in sheet.sheet_views)):
422
423 x = sheet.sheet_views[p.xsheet_view_name].view()[0]
424 y = sheet.sheet_views[p.ysheet_view_name].view()[0]
425
426 pylab.figure(figsize=(5,5))
427
428
429
430
431
432
433
434 isint=pylab.isinteractive()
435 pylab.ioff()
436 for r,c in zip(y,x):
437 pylab.plot(c,r,"k-")
438 for r,c in zip(transpose(y),transpose(x)):
439 pylab.plot(c,r,"k-")
440
441 pylab.xlabel('x')
442 pylab.ylabel('y')
443
444
445
446 pylab.axis(p.axis)
447 p.title='Topographic mapping to '+sheet.name+' at time '+topo.sim.timestr()
448
449 if isint: pylab.ion()
450 p.filename_suffix="_"+sheet.name
451 self._generate_figure(p)
452
453
455 """
456 Use matplotlib to make a plot combining a bitmap and line-based overlays.
457 """
458
459 plot_template = param.List(default=[{'Hue':'OrientationPreference'}],doc="""
460 Template for the underlying bitmap plot.""")
461
462 overlay = param.List(default=[('contours','OcularPreference',0.5,'black'),
463 ('arrows','DirectionPreference','DirectionSelectivity','white')],doc="""
464 List of overlaid plots, where each list item may be a 4-tuple
465 specifying either a contour line or a field of arrows::
466
467 ('contours',map-name,contour-value,line-color)
468
469 ('arrows',arrow-location-map-name,arrow-size-map-name,arrow-color)
470
471 Any number or combination of contours and arrows may be supplied.""")
472
473 normalize = param.Boolean(default='Individually',doc="""
474 Type of normalization, if any, to use. Options include 'None',
475 'Individually', and 'AllTogether'. See
476 topo.plotting.plotgroup.TemplatePlotGroup.normalize for more
477 details.""")
478
479
481 p=ParamOverrides(self,params)
482
483 for template in p.plot_template:
484
485 for sheet in topo.sim.objects(Sheet).values():
486 name=template.keys().pop(0)
487 plot=make_template_plot(template,sheet.sheet_views,sheet.xdensity,sheet.bounds,p.normalize,name=template[name])
488 if plot:
489 bitmap=plot.bitmap
490 pylab.figure(figsize=(5,5))
491 isint=pylab.isinteractive()
492 pylab.ioff()
493
494 pylab.imshow(bitmap.image,origin='lower',interpolation='nearest')
495 pylab.axis('off')
496
497 for (t,pref,sel,c) in p.overlay:
498 v = pylab.flipud(sheet.sheet_views[pref].view()[0])
499
500 if (t=='contours'):
501 pylab.contour(v,[sel,sel],colors=c,linewidths=2)
502
503 if (t=='arrows'):
504 s = pylab.flipud(sheet.sheet_views[sel].view()[0])
505 scale=int(pylab.ceil(log10(len(v))))
506 X=pylab.array([x for x in xrange(len(v)/scale)])
507 v_sc=pylab.zeros((len(v)/scale,len(v)/scale))
508 s_sc=pylab.zeros((len(v)/scale,len(v)/scale))
509 for i in X:
510 for j in X:
511 v_sc[i][j]=v[scale*i][scale*j]
512 s_sc[i][j]=s[scale*i][scale*j]
513 pylab.quiver(scale*X,scale*X,-cos(2*pi*v_sc)*s_sc,-sin(2*pi*v_sc)*s_sc,color=c,edgecolors=c,minshaft=3,linewidths=1)
514
515 p.title='%s overlaid with %s at time %s' %(plot.name,pref,topo.sim.timestr())
516 if isint: pylab.ion()
517 p.filename_suffix="_"+sheet.name
518 self._generate_figure(p)
519
520
521
523 """
524 Plot a tuning curve for a feature, such as orientation, contrast, or size.
525
526 The curve datapoints are collected from the curve_dict for
527 the units at the specified coordinates in the specified sheet
528 (where the units and sheet may be set by a GUI, using
529 topo.analysis.featureresponses.UnitCurveCommand.sheet and
530 topo.analysis.featureresponses.UnitCurveCommand.coords,
531 or by hand).
532 """
533
534 coords = param.List(default=[(0,0)],doc="""
535 List of coordinates of units to measure.""")
536
537 sheet = param.ObjectSelector(
538 default=None,doc="""
539 Name of the sheet to use in measurements.""")
540
541 x_axis = param.String(default="",doc="""
542 Feature to plot on the x axis of the tuning curve""")
543
544
545
546 plot_type = param.Callable(default=pylab.plot,doc="""
547 Matplotlib command to generate the plot.""")
548
549 unit = param.String(default="",doc="""
550 String to use in labels to specify the units in which curves are plotted.""")
551
552 __abstract = True
553
554
557
559 n = n % len(seq)
560 return seq[n:] + seq[:n]
561
563 """Return the x, y, and x ticks values for the specified curve from the curve_dict"""
564 x_values=sorted(curve.keys())
565 y_values=[curve[key].view()[0][i_value,j_value] for key in x_values]
566 return x_values,y_values,x_values
567
569 x = [];
570 y= [];
571 num_ticks = 5;
572 y.append(ticks[0])
573 x.append(0)
574 for i in xrange(0,num_ticks):
575 y.append(y[-1]+numpy.pi/(num_ticks+1));
576 x.append(x[-1]+numpy.pi/(num_ticks+1));
577 y.append(y[-1]+numpy.pi/(num_ticks+1));
578 x.append(3.14)
579 return (x,y)
580
581
583 p=ParamOverrides(self,params)
584 sheet = p.sheet
585 for coordinate in p.coords:
586 i_value,j_value=sheet.sheet2matrixidx(coordinate[0],coordinate[1])
587
588 f = pylab.figure(figsize=(7,7))
589 isint=pylab.isinteractive()
590 pylab.ioff()
591
592 pylab.ylabel('Response',fontsize='large')
593 pylab.xlabel('%s (%s)' % (p.x_axis.capitalize(),p.unit),fontsize='large')
594 pylab.title('Sheet %s, coordinate(x,y)=(%0.3f,%0.3f) at time %s' %
595 (sheet.name,coordinate[0],coordinate[1],topo.sim.timestr()))
596 p.title='%s: %s Tuning Curve' % (topo.sim.name,p.x_axis.capitalize())
597
598 self.first_curve=True
599 for curve_label in sorted(sheet.curve_dict[p.x_axis].keys()):
600 x_values,y_values,ticks=self._curve_values(i_value,j_value,sheet.curve_dict[p.x_axis][curve_label])
601
602 x_tick_values,ticks = self._reduce_ticks(ticks)
603 labels = [self._format_x_tick_label(x) for x in ticks]
604 pylab.xticks(x_tick_values, labels,fontsize='large')
605 pylab.yticks(fontsize='large')
606 p.plot_type(x_values, y_values, label=curve_label,lw=3.0)
607 self.first_curve=False
608
609 if isint: pylab.ion()
610 pylab.legend(loc=2)
611 self._generate_figure(p)
612
613
614
616 """
617 Same as tuning_curve, but rotates the curve so that minimum y
618 values are at the minimum x value to make the plots easier to
619 interpret. Such rotation is valid only for periodic quantities
620 like orientation or direction, and only if the correct period
621 is set.
622
623 At present, the y_values and labels are rotated by an amount
624 determined by the minmum y_value for the first curve plotted
625 (usually the lowest contrast curve).
626 """
627
628 cyclic_range = param.Number(default=pi,bounds=(0,None),softbounds=(0,10),doc="""
629 Range of the cyclic quantity (e.g. pi for the orientation of
630 a symmetric stimulus, or 2*pi for motion direction or the
631 orientation of a non-symmetric stimulus).""")
632
633 unit = param.String(default="degrees",doc="""
634 String to use in labels to specify the units in which curves are plotted.""")
635
636
637
638
639
640
643
644
646 """
647 Return the x, y, and x ticks values for the specified curve from the curve_dict.
648
649 With the current implementation, there may be cases (i.e.,
650 when the lowest contrast curve gives a lot of zero y_values)
651 in which the maximum is not in the center. This may
652 eventually be changed so that the preferred orientation is in
653 the center.
654 """
655 if self.first_curve==True:
656 x_values= sorted(curve.keys())
657 y_values=[curve[key].view()[0][i_value,j_value] for key in x_values]
658
659 min_arg=argmin(y_values)
660 x_min=x_values[min_arg]
661 y_min=y_values[min_arg]
662 y_values=self._rotate(y_values, n=min_arg)
663 self.ticks=self._rotate(x_values, n=min_arg)
664 self.ticks+=[x_min]
665 x_max=min(x_values)+self.cyclic_range
666 x_values.append(x_max)
667 y_values.append(y_min)
668
669 self.x_values=x_values
670 else:
671 y_values=[curve[key].view()[0][i_value,j_value] for key in self.ticks]
672
673 return self.x_values,y_values,self.ticks
674
675
676
678 """
679 Given a CF sheet receiving a CFProjection, plot
680 the mapping of the dests CF centers on the src sheet.
681 """
682 if isinstance(dest,str):
683 from topo import sim
684 dest = sim[dest]
685 plot_coord_mapping(dest.projections()[proj].coord_mapper,
686 dest,style=style)
687
688
689
691 """
692 Plot a coordinate mapping for a sheet.
693
694 Given a CoordinateMapperFn (as for a CFProjection) and a sheet
695 of the projection, plot a grid showing where the sheet's units
696 are mapped.
697 """
698
699 from pylab import plot,hold,ishold
700
701 xs = sheet.sheet_rows()
702 ys = sheet.sheet_cols()
703
704 hold_on = ishold()
705 if not hold_on:
706 plot()
707 hold(True)
708
709 for y in ys:
710 pts = [mapper(x,y) for x in xs]
711 plot([u for u,v in pts],
712 [v for u,v in pts],
713 style)
714
715 for x in xs:
716 pts = [mapper(x,y) for y in ys]
717 plot([u for u,v in pts],
718 [v for u,v in pts],
719 style)
720
721 hold(hold_on)
722
723
724