source: trunk/python/asapplotter.py@ 1105

Last change on this file since 1105 was 1101, checked in by mar637, 18 years ago

added default legend font size; added proper raise for wrong argument; fixed bug in set_range/slice indexing; added user defined fonts and sizes ( Ticket #56 )

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.6 KB
Line 
1from asap import rcParams, print_log, selector
2from numarray import logical_and
3
4class asapplotter:
5 """
6 The ASAP plotter.
7 By default the plotter is set up to plot polarisations
8 'colour stacked' and scantables across panels.
9 Note:
10 Currenly it only plots 'spectra' not Tsys or
11 other variables.
12 """
13 def __init__(self, visible=None):
14 self._visible = rcParams['plotter.gui']
15 if visible is not None:
16 self._visible = visible
17 self._plotter = self._newplotter()
18
19
20 self._panelling = None
21 self._stacking = None
22 self.set_panelling()
23 self.set_stacking()
24 self._rows = None
25 self._cols = None
26 self._autoplot = False
27 self._minmaxx = None
28 self._minmaxy = None
29 self._datamask = None
30 self._data = None
31 self._lmap = None
32 self._title = None
33 self._ordinate = None
34 self._abcissa = None
35 self._abcunit = None
36 self._usermask = []
37 self._maskselection = None
38 self._selection = selector()
39 self._hist = rcParams['plotter.histogram']
40 from matplotlib import rc as mplrc
41 mplrc('legend',fontsize=8)
42
43 def _translate(self, instr):
44 keys = "s b i p t".split()
45 if isinstance(instr, str):
46 for key in keys:
47 if instr.lower().startswith(key):
48 return key
49 return None
50
51 def _newplotter(self):
52 if self._visible:
53 from asap.asaplotgui import asaplotgui as asaplot
54 else:
55 from asap.asaplot import asaplot
56 return asaplot()
57
58
59 def plot(self, scan=None):
60 """
61 Plot a scantable.
62 Parameters:
63 scan: a scantable
64 Note:
65 If a scantable was specified in a previous call
66 to plot, no argument has to be given to 'replot'
67 NO checking is done that the abcissas of the scantable
68 are consistent e.g. all 'channel' or all 'velocity' etc.
69 """
70 if self._plotter.is_dead:
71 self._plotter = self._newplotter()
72 self._plotter.hold()
73 self._plotter.clear()
74 from asap import scantable
75 if not self._data and not scan:
76 msg = "Input is not a scantable"
77 if rcParams['verbose']:
78 print msg
79 return
80 raise TypeError(msg)
81 if isinstance(scan, scantable):
82 if self._data is not None:
83 if scan != self._data:
84 self._data = scan
85 # reset
86 self._reset()
87 else:
88 self._data = scan
89 self._reset()
90 # ranges become invalid when unit changes
91 if self._abcunit and self._abcunit != self._data.get_unit():
92 self._minmaxx = None
93 self._minmaxy = None
94 self._abcunit = self._data.get_unit()
95 self._datamask = None
96 self._plot(self._data)
97 if self._minmaxy is not None:
98 self._plotter.set_limits(ylim=self._minmaxy)
99 self._plotter.release()
100 print_log()
101 return
102
103 def set_mode(self, stacking=None, panelling=None):
104 """
105 Set the plots look and feel, i.e. what you want to see on the plot.
106 Parameters:
107 stacking: tell the plotter which variable to plot
108 as line color overlays (default 'pol')
109 panelling: tell the plotter which variable to plot
110 across multiple panels (default 'scan'
111 Note:
112 Valid modes are:
113 'beam' 'Beam' 'b': Beams
114 'if' 'IF' 'i': IFs
115 'pol' 'Pol' 'p': Polarisations
116 'scan' 'Scan' 's': Scans
117 'time' 'Time' 't': Times
118 """
119 msg = "Invalid mode"
120 if not self.set_panelling(panelling) or \
121 not self.set_stacking(stacking):
122 if rcParams['verbose']:
123 print msg
124 return
125 else:
126 raise TypeError(msg)
127 if self._data: self.plot(self._data)
128 return
129
130 def set_panelling(self, what=None):
131 mode = what
132 if mode is None:
133 mode = rcParams['plotter.panelling']
134 md = self._translate(mode)
135 if md:
136 self._panelling = md
137 self._title = None
138 return True
139 return False
140
141 def set_layout(self,rows=None,cols=None):
142 """
143 Set the multi-panel layout, i.e. how many rows and columns plots
144 are visible.
145 Parameters:
146 rows: The number of rows of plots
147 cols: The number of columns of plots
148 Note:
149 If no argument is given, the potter reverts to its auto-plot
150 behaviour.
151 """
152 self._rows = rows
153 self._cols = cols
154 if self._data: self.plot(self._data)
155 return
156
157 def set_stacking(self, what=None):
158 mode = what
159 if mode is None:
160 mode = rcParams['plotter.stacking']
161 md = self._translate(mode)
162 if md:
163 self._stacking = md
164 self._lmap = None
165 return True
166 return False
167
168 def set_range(self,xstart=None,xend=None,ystart=None,yend=None):
169 """
170 Set the range of interest on the abcissa of the plot
171 Parameters:
172 [x,y]start,[x,y]end: The start and end points of the 'zoom' window
173 Note:
174 These become non-sensical when the unit changes.
175 use plotter.set_range() without parameters to reset
176
177 """
178 if xstart is None and xend is None:
179 self._minmaxx = None
180 else:
181 self._minmaxx = [xstart,xend]
182 if ystart is None and yend is None:
183 self._minmaxy = None
184 else:
185 self._minmaxy = [ystart,yend]
186 if self._data: self.plot(self._data)
187 return
188
189 def set_legend(self, mp=None, fontsize = None, mode = 0):
190 """
191 Specify a mapping for the legend instead of using the default
192 indices:
193 Parameters:
194 mp: a list of 'strings'. This should have the same length
195 as the number of elements on the legend and then maps
196 to the indeces in order. It is possible to uses latex
197 math expression. These have to be enclosed in r'',
198 e.g. r'$x^{2}$'
199 fontsize: The font size of the label (default None)
200 mode: where to display the legend
201 Any other value for loc else disables the legend:
202 0: auto
203 1: upper right
204 2: upper left
205 3: lower left
206 4: lower right
207 5: right
208 6: center left
209 7: center right
210 8: lower center
211 9: upper center
212 10: center
213
214 Example:
215 If the data has two IFs/rest frequencies with index 0 and 1
216 for CO and SiO:
217 plotter.set_stacking('i')
218 plotter.set_legend(['CO','SiO'])
219 plotter.plot()
220 plotter.set_legend([r'$^{12}CO$', r'SiO'])
221 """
222 self._lmap = mp
223 self._plotter.legend(mode)
224 if isinstance(fontsize, int):
225 from matplotlib import rc as rcp
226 rcp('legend', fontsize=fontsize)
227 if self._data:
228 self.plot(self._data)
229 return
230
231 def set_title(self, title=None, fontsize=None):
232 """
233 Set the title of the plot. If multiple panels are plotted,
234 multiple titles have to be specified.
235 Example:
236 # two panels are visible on the plotter
237 plotter.set_title(["First Panel","Second Panel"])
238 """
239 self._title = title
240 if isinstance(fontsize, int):
241 from matplotlib import rc as rcp
242 rcp('axes', titlesize=fontsize)
243 if self._data: self.plot(self._data)
244 return
245
246 def set_ordinate(self, ordinate=None, fontsize=None):
247 """
248 Set the y-axis label of the plot. If multiple panels are plotted,
249 multiple labels have to be specified.
250 Parameters:
251 ordinate: a list of ordinate labels. None (default) let
252 data determine the labels
253 Example:
254 # two panels are visible on the plotter
255 plotter.set_ordinate(["First Y-Axis","Second Y-Axis"])
256 """
257 self._ordinate = ordinate
258 if isinstance(fontsize, int):
259 from matplotlib import rc as rcp
260 rcp('axes', labelsize=fontsize)
261 rcp('ytick', labelsize=fontsize)
262 if self._data: self.plot(self._data)
263 return
264
265 def set_abcissa(self, abcissa=None, fontsize=None):
266 """
267 Set the x-axis label of the plot. If multiple panels are plotted,
268 multiple labels have to be specified.
269 Parameters:
270 abcissa: a list of abcissa labels. None (default) let
271 data determine the labels
272 Example:
273 # two panels are visible on the plotter
274 plotter.set_ordinate(["First X-Axis","Second X-Axis"])
275 """
276 self._abcissa = abcissa
277 if isinstance(fontsize, int):
278 from matplotlib import rc as rcp
279 rcp('axes', labelsize=fontsize)
280 rcp('xtick', labelsize=fontsize)
281 if self._data: self.plot(self._data)
282 return
283
284 def set_colors(self, colormap):
285 """
286 Set the colors to be used. The plotter will cycle through
287 these colors when lines are overlaid (stacking mode).
288 Parameters:
289 colormap: a list of colour names
290 Example:
291 plotter.set_colors("red green blue")
292 # If for example four lines are overlaid e.g I Q U V
293 # 'I' will be 'red', 'Q' will be 'green', U will be 'blue'
294 # and 'V' will be 'red' again.
295 """
296 if isinstance(colormap,str):
297 colormap = colormap.split()
298 self._plotter.palette(0,colormap=colormap)
299 if self._data: self.plot(self._data)
300
301 def set_histogram(self, hist=True, linewidth=None):
302 """
303 Enable/Disable histogram-like plotting.
304 Parameters:
305 hist: True (default) or False. The fisrt default
306 is taken from the .asaprc setting
307 plotter.histogram
308 """
309 self._hist = hist
310 if isinstance(linewidth, float) or isinstance(linewidth, int):
311 from matplotlib import rc as rcp
312 rcp('lines', linewidth=linewidth)
313 if self._data: self.plot(self._data)
314
315 def set_linestyles(self, linestyles=None, linewidth=None):
316 """
317 Set the linestyles to be used. The plotter will cycle through
318 these linestyles when lines are overlaid (stacking mode) AND
319 only one color has been set.
320 Parameters:
321 linestyles: a list of linestyles to use.
322 'line', 'dashed', 'dotted', 'dashdot',
323 'dashdotdot' and 'dashdashdot' are
324 possible
325
326 Example:
327 plotter.set_colors("black")
328 plotter.set_linestyles("line dashed dotted dashdot")
329 # If for example four lines are overlaid e.g I Q U V
330 # 'I' will be 'solid', 'Q' will be 'dashed',
331 # U will be 'dotted' and 'V' will be 'dashdot'.
332 """
333 if isinstance(linestyles,str):
334 linestyles = linestyles.split()
335 self._plotter.palette(color=0,linestyle=0,linestyles=linestyles)
336 if isinstance(linewidth, float) or isinstance(linewidth, int):
337 from matplotlib import rc as rcp
338 rcp('lines', linewidth=linewidth)
339 if self._data: self.plot(self._data)
340
341 def set_font(self, family=None, style=None, weight=None, size=None):
342 """
343 Set font properties.
344 Parameters:
345 family: one of 'sans-serif', 'serif', 'cursive', 'fantasy', 'monospace'
346 style: one of 'normal' (or 'roman'), 'italic' or 'oblique'
347 weight: one of 'normal or 'bold'
348 size: the 'general' font size, individual elements can be adjusted
349 seperately
350 """
351 from matplotlib import rc as rcp
352 if isinstance(family, str):
353 rcp('font', family=family)
354 if isinstance(style, str):
355 rcp('font', style=style)
356 if isinstance(weight, str):
357 rcp('font', weight=weight)
358 if isinstance(size, float) or isinstance(size, int):
359 rcp('font', size=size)
360 if self._data: self.plot(self._data)
361
362 def save(self, filename=None, orientation=None, dpi=None):
363 """
364 Save the plot to a file. The know formats are 'png', 'ps', 'eps'.
365 Parameters:
366 filename: The name of the output file. This is optional
367 and autodetects the image format from the file
368 suffix. If non filename is specified a file
369 called 'yyyymmdd_hhmmss.png' is created in the
370 current directory.
371 orientation: optional parameter for postscript only (not eps).
372 'landscape', 'portrait' or None (default) are valid.
373 If None is choosen for 'ps' output, the plot is
374 automatically oriented to fill the page.
375 dpi: The dpi of the output non-ps plot
376 """
377 self._plotter.save(filename,orientation,dpi)
378 return
379
380
381 def set_mask(self, mask=None, selection=None):
382 """
383 Set a plotting mask for a specific polarization.
384 This is useful for masking out "noise" Pangle outside a source.
385 Parameters:
386 mask: a mask from scantable.create_mask
387 selection: the spectra to apply the mask to.
388 Example:
389 select = selector()
390 select.setpolstrings("Pangle")
391 plotter.set_mask(mymask, select)
392 """
393 if not self._data:
394 msg = "Can only set mask after a first call to plot()"
395 if rcParams['verbose']:
396 print msg
397 return
398 else:
399 raise RuntimeError(msg)
400 if len(mask):
401 if isinstance(mask, list) or isinstance(mask, tuple):
402 self._usermask = array(mask)
403 else:
404 self._usermask = mask
405 if mask is None and selection is None:
406 self._usermask = []
407 self._maskselection = None
408 if isinstance(selection, selector):
409 self._maskselection = {'b': selection.get_beams(),
410 's': selection.get_scans(),
411 'i': selection.get_ifs(),
412 'p': selection.get_pols(),
413 't': [] }
414 else:
415 self._maskselection = None
416 self.plot(self._data)
417
418 def _slice_indeces(self, data):
419 mn = self._minmaxx[0]
420 mx = self._minmaxx[1]
421 asc = data[0] < data[-1]
422 start=0
423 end = len(data)-1
424 inc = 1
425 if not asc:
426 start = len(data)-1
427 end = 0
428 inc = -1
429 # find min index
430 while start > 0 and data[start] < mn:
431 start+= inc
432 # find max index
433 while end > 0 and data[end] > mx:
434 end-=inc
435 if end > 0: end +=1
436 if start > end:
437 return end,start
438 return start,end
439
440 def _reset(self):
441 self._usermask = []
442 self._usermaskspectra = None
443 self.set_selection(None, False)
444
445 def _plot(self, scan):
446 savesel = scan.get_selection()
447 sel = savesel + self._selection
448 d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
449 'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME' }
450 order = [d0[self._panelling],d0[self._stacking]]
451 sel.set_order(order)
452 scan.set_selection(sel)
453 d = {'b': scan.getbeam, 's': scan.getscan,
454 'i': scan.getif, 'p': scan.getpol, 't': scan._gettime }
455
456 polmodes = dict(zip(self._selection.get_pols(),self._selection.get_poltypes()))
457 n,nstack = self._get_selected_n(scan)
458 maxpanel, maxstack = 16,8
459 if n > maxpanel or nstack > maxstack:
460 from asap import asaplog
461 msg ="Scan to be plotted contains more than %d selections.\n" \
462 "Selecting first %d selections..." % (maxpanel,maxpanel)
463 asaplog.push(msg)
464 print_log()
465 n = min(n,maxpanel)
466 nstack = min(nstack,maxstack)
467
468 if n > 1:
469 ganged = rcParams['plotter.ganged']
470 if self._rows and self._cols:
471 n = min(n,self._rows*self._cols)
472 self._plotter.set_panels(rows=self._rows,cols=self._cols,
473 nplots=n,ganged=ganged)
474 else:
475 self._plotter.set_panels(rows=n,cols=0,nplots=n,ganged=ganged)
476 else:
477 self._plotter.set_panels()
478 r=0
479 nr = scan.nrow()
480 a0,b0 = -1,-1
481 allxlim = []
482 allylim = []
483 newpanel=True
484 panelcount,stackcount = 0,0
485 while r < nr:
486 a = d[self._panelling](r)
487 b = d[self._stacking](r)
488 if a > a0 and panelcount < n:
489 if n > 1:
490 self._plotter.subplot(panelcount)
491 self._plotter.palette(0)
492 #title
493 xlab = self._abcissa and self._abcissa[panelcount] \
494 or scan._getabcissalabel()
495 ylab = self._ordinate and self._ordinate[panelcount] \
496 or scan._get_ordinate_label()
497 self._plotter.set_axes('xlabel',xlab)
498 self._plotter.set_axes('ylabel',ylab)
499 lbl = self._get_label(scan, r, self._panelling, self._title)
500 if isinstance(lbl, list) or isinstance(lbl, tuple):
501 if 0 <= panelcount < len(lbl):
502 lbl = lbl[panelcount]
503 else:
504 # get default label
505 lbl = self._get_label(scan, r, self._panelling, None)
506 self._plotter.set_axes('title',lbl)
507 newpanel = True
508 stackcount =0
509 panelcount += 1
510 if (b > b0 or newpanel) and stackcount < nstack:
511 y = []
512 if len(polmodes):
513 y = scan._getspectrum(r, polmodes[scan.getpol(r)])
514 else:
515 y = scan._getspectrum(r)
516 m = scan._getmask(r)
517 if self._maskselection and len(self._usermask) == len(m):
518 if d[self._stacking](r) in self._maskselection[self._stacking]:
519 m = logical_and(m, self._usermask)
520 x = scan._getabcissa(r)
521 from matplotlib.numerix import ma,logical_not,array
522 y = ma.MA.MaskedArray(y,mask=logical_not(array(m,copy=0)),copy=0)
523 if self._minmaxx is not None:
524 s,e = self._slice_indeces(x)
525 x = x[s:e]
526 y = y[s:e]
527 if len(x) > 1024 and rcParams['plotter.decimate']:
528 fac = len(x)/1024
529 x = x[::fac]
530 y = y[::fac]
531 llbl = self._get_label(scan, r, self._stacking, self._lmap)
532 if isinstance(llbl, list) or isinstance(llbl, tuple):
533 if 0 <= stackcount < len(llbl):
534 # use user label
535 llbl = llbl[stackcount]
536 else:
537 # get default label
538 llbl = self._get_label(scan, r, self._stacking, None)
539 self._plotter.set_line(label=llbl)
540 plotit = self._plotter.plot
541 if self._hist: plotit = self._plotter.hist
542 if len(x) > 0: plotit(x,y)
543 xlim= self._minmaxx or [min(x),max(x)]
544 allxlim += xlim
545 ylim= self._minmaxy or [ma.MA.minimum(y),ma.MA.maximum(y)]
546 allylim += ylim
547 stackcount += 1
548 # last in colour stack -> autoscale x
549 if stackcount == nstack:
550 allxlim.sort()
551 self._plotter.axes.set_xlim([allxlim[0],allxlim[-1]])
552 # clear
553 allxlim =[]
554
555 newpanel = False
556 a0=a
557 b0=b
558 # ignore following rows
559 if (panelcount == n) and (stackcount == nstack):
560 # last panel -> autoscale y if ganged
561 if rcParams['plotter.ganged']:
562 allylim.sort()
563 self._plotter.set_limits(ylim=[allylim[0],allylim[-1]])
564 break
565 r+=1 # next row
566 #reset the selector to the scantable's original
567 scan.set_selection(savesel)
568
569 def set_selection(self, selection=None, refresh=True):
570 self._selection = isinstance(selection,selector) and selection or selector()
571 d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
572 'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME' }
573 order = [d0[self._panelling],d0[self._stacking]]
574 self._selection.set_order(order)
575 if self._data and refresh: self.plot(self._data)
576
577 def _get_selected_n(self, scan):
578 d1 = {'b': scan.nbeam, 's': scan.nscan,
579 'i': scan.nif, 'p': scan.npol, 't': scan.ncycle }
580 d2 = { 'b': len(self._selection.get_beams()),
581 's': len(self._selection.get_scans()),
582 'i': len(self._selection.get_ifs()),
583 'p': len(self._selection.get_pols()),
584 't': len(self._selection.get_cycles()) }
585 n = d2[self._panelling] or d1[self._panelling]()
586 nstack = d2[self._stacking] or d1[self._stacking]()
587 return n,nstack
588
589 def _get_label(self, scan, row, mode, userlabel=None):
590 pms = dict(zip(self._selection.get_pols(),self._selection.get_poltypes()))
591 if len(pms):
592 poleval = scan._getpollabel(scan.getpol(row),pms[scan.getpol(row)])
593 else:
594 poleval = scan._getpollabel(scan.getpol(row),scan.poltype())
595 d = {'b': "Beam "+str(scan.getbeam(row)),
596 's': scan._getsourcename(row),
597 'i': "IF"+str(scan.getif(row)),
598 'p': poleval,
599 't': scan._gettime(row) }
600 return userlabel or d[mode]
Note: See TracBrowser for help on using the repository browser.