source: trunk/python/asapplotter.py@ 1152

Last change on this file since 1152 was 1148, checked in by mar637, 18 years ago

use get*nos instead of n* (* can be scan, beam etc.). This showed a bug in c+ which was fixed.

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