source: trunk/python/asapplotter.py@ 1894

Last change on this file since 1894 was 1862, checked in by Malte Marquarding, 14 years ago

renamed print_log_dec to more explicit asaplog_post_dec

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 52.0 KB
Line 
1from asap.parameters import rcParams
2from asap.selector import selector
3from asap.scantable import scantable
4from asap.logging import asaplog, asaplog_post_dec
5import matplotlib.axes
6from matplotlib.font_manager import FontProperties
7from matplotlib.text import Text
8
9import re
10
11class asapplotter:
12 """
13 The ASAP plotter.
14 By default the plotter is set up to plot polarisations
15 'colour stacked' and scantables across panels.
16
17 .. note::
18
19 Currenly it only plots 'spectra' not Tsys or
20 other variables.
21
22 """
23 def __init__(self, visible=None , **kwargs):
24 self._visible = rcParams['plotter.gui']
25 if visible is not None:
26 self._visible = visible
27 self._plotter = self._newplotter(**kwargs)
28 # additional tool bar
29 self._plotter.figmgr.casabar=self._newcasabar()
30
31 self._panelling = None
32 self._stacking = None
33 self.set_panelling()
34 self.set_stacking()
35 self._rows = None
36 self._cols = None
37 self._autoplot = False
38 self._minmaxx = None
39 self._minmaxy = None
40 self._datamask = None
41 self._data = None
42 self._lmap = None
43 self._title = None
44 self._ordinate = None
45 self._abcissa = None
46 self._abcunit = None
47 self._usermask = []
48 self._maskselection = None
49 self._selection = selector()
50 self._hist = rcParams['plotter.histogram']
51 self._fp = FontProperties()
52 self._panellayout = self.set_panellayout(refresh=False)
53
54 def _translate(self, instr):
55 keys = "s b i p t".split()
56 if isinstance(instr, str):
57 for key in keys:
58 if instr.lower().startswith(key):
59 return key
60 return None
61
62 def _newplotter(self, **kwargs):
63 backend=matplotlib.get_backend()
64 if not self._visible:
65 from asap.asaplot import asaplot
66 elif backend == 'TkAgg':
67 from asap.asaplotgui import asaplotgui as asaplot
68 elif backend == 'Qt4Agg':
69 from asap.asaplotgui_qt4 import asaplotgui as asaplot
70 elif backend == 'GTkAgg':
71 from asap.asaplotgui_gtk import asaplotgui as asaplot
72 else:
73 from asap.asaplot import asaplot
74 return asaplot(**kwargs)
75
76 def _newcasabar(self):
77 backend=matplotlib.get_backend()
78 if self._visible and backend == "TkAgg":
79 from asap.casatoolbar import CustomToolbarTkAgg
80 return CustomToolbarTkAgg(self)
81 else: return None
82
83 @asaplog_post_dec
84 def plot(self, scan=None):
85 """
86 Plot a scantable.
87 Parameters:
88 scan: a scantable
89 Note:
90 If a scantable was specified in a previous call
91 to plot, no argument has to be given to 'replot'
92 NO checking is done that the abcissas of the scantable
93 are consistent e.g. all 'channel' or all 'velocity' etc.
94 """
95 if self._plotter.is_dead:
96 if hasattr(self._plotter.figmgr,'casabar'):
97 del self._plotter.figmgr.casabar
98 self._plotter = self._newplotter()
99 self._plotter.figmgr.casabar=self._newcasabar()
100 self._plotter.hold()
101 self._plotter.clear()
102 if not self._data and not scan:
103 msg = "Input is not a scantable"
104 raise TypeError(msg)
105 if scan: self.set_data(scan,refresh=False)
106 self._plot(self._data)
107 if self._minmaxy is not None:
108 self._plotter.set_limits(ylim=self._minmaxy)
109 if self._plotter.figmgr.casabar: self._plotter.figmgr.casabar.enable_button()
110 self._plotter.release()
111 self._plotter.tidy()
112 self._plotter.show(hardrefresh=False)
113 return
114
115 def gca(self):
116 return self._plotter.figure.gca()
117
118 def refresh(self):
119 """Do a soft refresh"""
120 self._plotter.figure.show()
121
122 def create_mask(self, nwin=1, panel=0, color=None):
123 """
124 Interactively define a mask.It retruns a mask that is equivalent to
125 the one created manually with scantable.create_mask.
126 Parameters:
127 nwin: The number of mask windows to create interactively
128 default is 1.
129 panel: Which panel to use for mask selection. This is useful
130 if different IFs are spread over panels (default 0)
131 """
132 if self._data is None:
133 return []
134 outmask = []
135 self._plotter.subplot(panel)
136 xmin, xmax = self._plotter.axes.get_xlim()
137 marg = 0.05*(xmax-xmin)
138 self._plotter.axes.set_xlim(xmin-marg, xmax+marg)
139 self.refresh()
140
141 def cleanup(lines=False, texts=False, refresh=False):
142 if lines:
143 del self._plotter.axes.lines[-1]
144 if texts:
145 del self._plotter.axes.texts[-1]
146 if refresh:
147 self.refresh()
148
149 for w in xrange(nwin):
150 wpos = []
151 self.text(0.05,1.0, "Add start boundary",
152 coords="relative", fontsize=10)
153 point = self._plotter.get_point()
154 cleanup(texts=True)
155 if point is None:
156 continue
157 wpos.append(point[0])
158 self.axvline(wpos[0], color=color)
159 self.text(0.05,1.0, "Add end boundary", coords="relative", fontsize=10)
160 point = self._plotter.get_point()
161 cleanup(texts=True, lines=True)
162 if point is None:
163 self.refresh()
164 continue
165 wpos.append(point[0])
166 self.axvspan(wpos[0], wpos[1], alpha=0.1,
167 edgecolor=color, facecolor=color)
168 ymin, ymax = self._plotter.axes.get_ylim()
169 outmask.append(wpos)
170
171 self._plotter.axes.set_xlim(xmin, xmax)
172 self.refresh()
173 if len(outmask) > 0:
174 return self._data.create_mask(*outmask)
175 return []
176
177 # forwards to matplotlib axes
178 def text(self, *args, **kwargs):
179 if kwargs.has_key("interactive"):
180 if kwargs.pop("interactive"):
181 pos = self._plotter.get_point()
182 args = tuple(pos)+args
183 self._axes_callback("text", *args, **kwargs)
184
185 text.__doc__ = matplotlib.axes.Axes.text.__doc__
186
187 def arrow(self, *args, **kwargs):
188 if kwargs.has_key("interactive"):
189 if kwargs.pop("interactive"):
190 pos = self._plotter.get_region()
191 dpos = (pos[0][0], pos[0][1],
192 pos[1][0]-pos[0][0],
193 pos[1][1] - pos[0][1])
194 args = dpos + args
195 self._axes_callback("arrow", *args, **kwargs)
196
197 arrow.__doc__ = matplotlib.axes.Axes.arrow.__doc__
198
199 def annotate(self, text, xy=None, xytext=None, **kwargs):
200 if kwargs.has_key("interactive"):
201 if kwargs.pop("interactive"):
202 xy = self._plotter.get_point()
203 xytext = self._plotter.get_point()
204 if not kwargs.has_key("arrowprops"):
205 kwargs["arrowprops"] = dict(arrowstyle="->")
206 self._axes_callback("annotate", text, xy, xytext, **kwargs)
207
208 annotate.__doc__ = matplotlib.axes.Axes.annotate.__doc__
209
210 def axvline(self, *args, **kwargs):
211 if kwargs.has_key("interactive"):
212 if kwargs.pop("interactive"):
213 pos = self._plotter.get_point()
214 args = (pos[0],)+args
215 self._axes_callback("axvline", *args, **kwargs)
216
217 axvline.__doc__ = matplotlib.axes.Axes.axvline.__doc__
218
219 def axhline(self, *args, **kwargs):
220 if kwargs.has_key("interactive"):
221 if kwargs.pop("interactive"):
222 pos = self._plotter.get_point()
223 args = (pos[1],)+args
224 self._axes_callback("axhline", *args, **kwargs)
225
226 axhline.__doc__ = matplotlib.axes.Axes.axhline.__doc__
227
228 def axvspan(self, *args, **kwargs):
229 if kwargs.has_key("interactive"):
230 if kwargs.pop("interactive"):
231 pos = self._plotter.get_region()
232 dpos = (pos[0][0], pos[1][0])
233 args = dpos + args
234 self._axes_callback("axvspan", *args, **kwargs)
235 # hack to preventy mpl from redrawing the patch
236 # it seem to convert the patch into lines on every draw.
237 # This doesn't happen in a test script???
238 #del self._plotter.axes.patches[-1]
239
240 axvspan.__doc__ = matplotlib.axes.Axes.axvspan.__doc__
241
242 def axhspan(self, *args, **kwargs):
243 if kwargs.has_key("interactive"):
244 if kwargs.pop("interactive"):
245 pos = self._plotter.get_region()
246 dpos = (pos[0][1], pos[1][1])
247 args = dpos + args
248 self._axes_callback("axhspan", *args, **kwargs)
249 # hack to preventy mpl from redrawing the patch
250 # it seem to convert the patch into lines on every draw.
251 # This doesn't happen in a test script???
252 #del self._plotter.axes.patches[-1]
253
254 axhspan.__doc__ = matplotlib.axes.Axes.axhspan.__doc__
255
256 def _axes_callback(self, axesfunc, *args, **kwargs):
257 panel = 0
258 if kwargs.has_key("panel"):
259 panel = kwargs.pop("panel")
260 coords = None
261 if kwargs.has_key("coords"):
262 coords = kwargs.pop("coords")
263 if coords.lower() == 'world':
264 kwargs["transform"] = self._plotter.axes.transData
265 elif coords.lower() == 'relative':
266 kwargs["transform"] = self._plotter.axes.transAxes
267 self._plotter.subplot(panel)
268 self._plotter.axes.set_autoscale_on(False)
269 getattr(self._plotter.axes, axesfunc)(*args, **kwargs)
270 self._plotter.show(False)
271 self._plotter.axes.set_autoscale_on(True)
272 # end matplotlib.axes fowarding functions
273
274 @asaplog_post_dec
275 def set_data(self, scan, refresh=True):
276 """
277 Set a scantable to plot.
278 Parameters:
279 scan: a scantable
280 refresh: True (default) or False. If True, the plot is
281 replotted based on the new parameter setting(s).
282 Otherwise,the parameter(s) are set without replotting.
283 Note:
284 The user specified masks and data selections will be reset
285 if a new scantable is set. This method should be called before
286 setting data selections (set_selection) and/or masks (set_mask).
287 """
288 from asap import scantable
289 if isinstance(scan, scantable):
290 if self._data is not None:
291 if scan != self._data:
292 self._data = scan
293 # reset
294 self._reset()
295 msg = "A new scantable is set to the plotter. The masks and data selections are reset."
296 asaplog.push( msg )
297 else:
298 self._data = scan
299 self._reset()
300 else:
301 msg = "Input is not a scantable"
302 raise TypeError(msg)
303
304 # ranges become invalid when unit changes
305 if self._abcunit and self._abcunit != self._data.get_unit():
306 self._minmaxx = None
307 self._minmaxy = None
308 self._abcunit = self._data.get_unit()
309 self._datamask = None
310 if refresh: self.plot()
311
312 @asaplog_post_dec
313 def set_mode(self, stacking=None, panelling=None, refresh=True):
314 """
315 Set the plots look and feel, i.e. what you want to see on the plot.
316 Parameters:
317 stacking: tell the plotter which variable to plot
318 as line colour overlays (default 'pol')
319 panelling: tell the plotter which variable to plot
320 across multiple panels (default 'scan'
321 refresh: True (default) or False. If True, the plot is
322 replotted based on the new parameter setting(s).
323 Otherwise,the parameter(s) are set without replotting.
324 Note:
325 Valid modes are:
326 'beam' 'Beam' 'b': Beams
327 'if' 'IF' 'i': IFs
328 'pol' 'Pol' 'p': Polarisations
329 'scan' 'Scan' 's': Scans
330 'time' 'Time' 't': Times
331 """
332 msg = "Invalid mode"
333 if not self.set_panelling(panelling) or \
334 not self.set_stacking(stacking):
335 raise TypeError(msg)
336 if refresh and self._data: self.plot(self._data)
337 return
338
339 def set_panelling(self, what=None):
340 """Set the 'panelling' mode i.e. which type of spectra should be
341 spread across different panels.
342 """
343
344 mode = what
345 if mode is None:
346 mode = rcParams['plotter.panelling']
347 md = self._translate(mode)
348 if md:
349 self._panelling = md
350 self._title = None
351 return True
352 return False
353
354 def set_layout(self,rows=None,cols=None,refresh=True):
355 """
356 Set the multi-panel layout, i.e. how many rows and columns plots
357 are visible.
358 Parameters:
359 rows: The number of rows of plots
360 cols: The number of columns of plots
361 refresh: True (default) or False. If True, the plot is
362 replotted based on the new parameter setting(s).
363 Otherwise,the parameter(s) are set without replotting.
364 Note:
365 If no argument is given, the potter reverts to its auto-plot
366 behaviour.
367 """
368 self._rows = rows
369 self._cols = cols
370 if refresh and self._data: self.plot(self._data)
371 return
372
373 def set_stacking(self, what=None):
374 """Set the 'stacking' mode i.e. which type of spectra should be
375 overlayed.
376 """
377 mode = what
378 if mode is None:
379 mode = rcParams['plotter.stacking']
380 md = self._translate(mode)
381 if md:
382 self._stacking = md
383 self._lmap = None
384 return True
385 return False
386
387 def set_range(self,xstart=None,xend=None,ystart=None,yend=None,refresh=True):
388 """
389 Set the range of interest on the abcissa of the plot
390 Parameters:
391 [x,y]start,[x,y]end: The start and end points of the 'zoom' window
392 refresh: True (default) or False. If True, the plot is
393 replotted based on the new parameter setting(s).
394 Otherwise,the parameter(s) are set without replotting.
395 Note:
396 These become non-sensical when the unit changes.
397 use plotter.set_range() without parameters to reset
398
399 """
400 if xstart is None and xend is None:
401 self._minmaxx = None
402 else:
403 self._minmaxx = [xstart,xend]
404 if ystart is None and yend is None:
405 self._minmaxy = None
406 else:
407 self._minmaxy = [ystart,yend]
408 if refresh and self._data: self.plot(self._data)
409 return
410
411 def set_legend(self, mp=None, fontsize = None, mode = 0, refresh=True):
412 """
413 Specify a mapping for the legend instead of using the default
414 indices:
415 Parameters:
416 mp: a list of 'strings'. This should have the same length
417 as the number of elements on the legend and then maps
418 to the indeces in order. It is possible to uses latex
419 math expression. These have to be enclosed in r'',
420 e.g. r'$x^{2}$'
421 fontsize: The font size of the label (default None)
422 mode: where to display the legend
423 Any other value for loc else disables the legend:
424 0: auto
425 1: upper right
426 2: upper left
427 3: lower left
428 4: lower right
429 5: right
430 6: center left
431 7: center right
432 8: lower center
433 9: upper center
434 10: center
435 refresh: True (default) or False. If True, the plot is
436 replotted based on the new parameter setting(s).
437 Otherwise,the parameter(s) are set without replotting.
438
439 Example:
440 If the data has two IFs/rest frequencies with index 0 and 1
441 for CO and SiO:
442 plotter.set_stacking('i')
443 plotter.set_legend(['CO','SiO'])
444 plotter.plot()
445 plotter.set_legend([r'$^{12}CO$', r'SiO'])
446 """
447 self._lmap = mp
448 self._plotter.legend(mode)
449 if isinstance(fontsize, int):
450 from matplotlib import rc as rcp
451 rcp('legend', fontsize=fontsize)
452 if refresh and self._data: self.plot(self._data)
453 return
454
455 def set_title(self, title=None, fontsize=None, refresh=True):
456 """
457 Set the title of the plot. If multiple panels are plotted,
458 multiple titles have to be specified.
459 Parameters:
460 refresh: True (default) or False. If True, the plot is
461 replotted based on the new parameter setting(s).
462 Otherwise,the parameter(s) are set without replotting.
463 Example:
464 # two panels are visible on the plotter
465 plotter.set_title(["First Panel","Second Panel"])
466 """
467 self._title = title
468 if isinstance(fontsize, int):
469 from matplotlib import rc as rcp
470 rcp('axes', titlesize=fontsize)
471 if refresh and self._data: self.plot(self._data)
472 return
473
474 def set_ordinate(self, ordinate=None, fontsize=None, refresh=True):
475 """
476 Set the y-axis label of the plot. If multiple panels are plotted,
477 multiple labels have to be specified.
478 Parameters:
479 ordinate: a list of ordinate labels. None (default) let
480 data determine the labels
481 refresh: True (default) or False. If True, the plot is
482 replotted based on the new parameter setting(s).
483 Otherwise,the parameter(s) are set without replotting.
484 Example:
485 # two panels are visible on the plotter
486 plotter.set_ordinate(["First Y-Axis","Second Y-Axis"])
487 """
488 self._ordinate = ordinate
489 if isinstance(fontsize, int):
490 from matplotlib import rc as rcp
491 rcp('axes', labelsize=fontsize)
492 rcp('ytick', labelsize=fontsize)
493 if refresh and self._data: self.plot(self._data)
494 return
495
496 def set_abcissa(self, abcissa=None, fontsize=None, refresh=True):
497 """
498 Set the x-axis label of the plot. If multiple panels are plotted,
499 multiple labels have to be specified.
500 Parameters:
501 abcissa: a list of abcissa labels. None (default) let
502 data determine the labels
503 refresh: True (default) or False. If True, the plot is
504 replotted based on the new parameter setting(s).
505 Otherwise,the parameter(s) are set without replotting.
506 Example:
507 # two panels are visible on the plotter
508 plotter.set_ordinate(["First X-Axis","Second X-Axis"])
509 """
510 self._abcissa = abcissa
511 if isinstance(fontsize, int):
512 from matplotlib import rc as rcp
513 rcp('axes', labelsize=fontsize)
514 rcp('xtick', labelsize=fontsize)
515 if refresh and self._data: self.plot(self._data)
516 return
517
518 def set_colors(self, colmap, refresh=True):
519 """
520 Set the colours to be used. The plotter will cycle through
521 these colours when lines are overlaid (stacking mode).
522 Parameters:
523 colmap: a list of colour names
524 refresh: True (default) or False. If True, the plot is
525 replotted based on the new parameter setting(s).
526 Otherwise,the parameter(s) are set without replotting.
527 Example:
528 plotter.set_colors("red green blue")
529 # If for example four lines are overlaid e.g I Q U V
530 # 'I' will be 'red', 'Q' will be 'green', U will be 'blue'
531 # and 'V' will be 'red' again.
532 """
533 if isinstance(colmap,str):
534 colmap = colmap.split()
535 self._plotter.palette(0, colormap=colmap)
536 if refresh and self._data: self.plot(self._data)
537
538 # alias for english speakers
539 set_colours = set_colors
540
541 def set_histogram(self, hist=True, linewidth=None, refresh=True):
542 """
543 Enable/Disable histogram-like plotting.
544 Parameters:
545 hist: True (default) or False. The fisrt default
546 is taken from the .asaprc setting
547 plotter.histogram
548 refresh: True (default) or False. If True, the plot is
549 replotted based on the new parameter setting(s).
550 Otherwise,the parameter(s) are set without replotting.
551 """
552 self._hist = hist
553 if isinstance(linewidth, float) or isinstance(linewidth, int):
554 from matplotlib import rc as rcp
555 rcp('lines', linewidth=linewidth)
556 if refresh and self._data: self.plot(self._data)
557
558 def set_linestyles(self, linestyles=None, linewidth=None, refresh=True):
559 """
560 Set the linestyles to be used. The plotter will cycle through
561 these linestyles when lines are overlaid (stacking mode) AND
562 only one color has been set.
563 Parameters:
564 linestyles: a list of linestyles to use.
565 'line', 'dashed', 'dotted', 'dashdot',
566 'dashdotdot' and 'dashdashdot' are
567 possible
568 refresh: True (default) or False. If True, the plot is
569 replotted based on the new parameter setting(s).
570 Otherwise,the parameter(s) are set without replotting.
571 Example:
572 plotter.set_colors("black")
573 plotter.set_linestyles("line dashed dotted dashdot")
574 # If for example four lines are overlaid e.g I Q U V
575 # 'I' will be 'solid', 'Q' will be 'dashed',
576 # U will be 'dotted' and 'V' will be 'dashdot'.
577 """
578 if isinstance(linestyles,str):
579 linestyles = linestyles.split()
580 self._plotter.palette(color=0,linestyle=0,linestyles=linestyles)
581 if isinstance(linewidth, float) or isinstance(linewidth, int):
582 from matplotlib import rc as rcp
583 rcp('lines', linewidth=linewidth)
584 if refresh and self._data: self.plot(self._data)
585
586 def set_font(self, refresh=True,**kwargs):
587 """
588 Set font properties.
589 Parameters:
590 family: one of 'sans-serif', 'serif', 'cursive', 'fantasy', 'monospace'
591 style: one of 'normal' (or 'roman'), 'italic' or 'oblique'
592 weight: one of 'normal or 'bold'
593 size: the 'general' font size, individual elements can be adjusted
594 seperately
595 refresh: True (default) or False. If True, the plot is
596 replotted based on the new parameter setting(s).
597 Otherwise,the parameter(s) are set without replotting.
598 """
599 from matplotlib import rc as rcp
600 fdict = {}
601 for k,v in kwargs.iteritems():
602 if v:
603 fdict[k] = v
604 self._fp = FontProperties(**fdict)
605 if refresh and self._data: self.plot(self._data)
606
607 def set_panellayout(self,layout=[],refresh=True):
608 """
609 Set the layout of subplots.
610 Parameters:
611 layout: a list of subplots layout in figure coordinate (0-1),
612 i.e., fraction of the figure width or height.
613 The order of elements should be:
614 [left, bottom, right, top, horizontal space btw panels,
615 vertical space btw panels].
616 refresh: True (default) or False. If True, the plot is
617 replotted based on the new parameter setting(s).
618 Otherwise,the parameter(s) are set without replotting.
619 Note
620 * When layout is not specified, the values are reset to the defaults
621 of matplotlib.
622 * If any element is set to be None, the current value is adopted.
623 """
624 if layout == []: self._panellayout=self._reset_panellayout()
625 else:
626 self._panellayout=[None]*6
627 self._panellayout[0:len(layout)]=layout
628 #print "panel layout set to ",self._panellayout
629 if refresh and self._data: self.plot(self._data)
630
631 def _reset_panellayout(self):
632 ks=map(lambda x: 'figure.subplot.'+x,
633 ['left','bottom','right','top','hspace','wspace'])
634 return map(matplotlib.rcParams.get,ks)
635
636 def plot_lines(self, linecat=None, doppler=0.0, deltachan=10, rotate=90.0,
637 location=None):
638 """
639 Plot a line catalog.
640 Parameters:
641 linecat: the linecatalog to plot
642 doppler: the velocity shift to apply to the frequencies
643 deltachan: the number of channels to include each side of the
644 line to determine a local maximum/minimum
645 rotate: the rotation (in degrees) )for the text label (default 90.0)
646 location: the location of the line annotation from the 'top',
647 'bottom' or alternate (None - the default)
648 Notes:
649 If the spectrum is flagged no line will be drawn in that location.
650 """
651 if not self._data:
652 raise RuntimeError("No scantable has been plotted yet.")
653 from asap._asap import linecatalog
654 if not isinstance(linecat, linecatalog):
655 raise ValueError("'linecat' isn't of type linecatalog.")
656 if not self._data.get_unit().endswith("Hz"):
657 raise RuntimeError("Can only overlay linecatalogs when data is in frequency.")
658 from numpy import ma
659 for j in range(len(self._plotter.subplots)):
660 self._plotter.subplot(j)
661 lims = self._plotter.axes.get_xlim()
662 for row in range(linecat.nrow()):
663 # get_frequency returns MHz
664 base = { "GHz": 1000.0, "MHz": 1.0, "Hz": 1.0e-6 }
665 restf = linecat.get_frequency(row)/base[self._data.get_unit()]
666 c = 299792.458
667 freq = restf*(1.0-doppler/c)
668 if lims[0] < freq < lims[1]:
669 if location is None:
670 loc = 'bottom'
671 if row%2: loc='top'
672 else: loc = location
673 maxys = []
674 for line in self._plotter.axes.lines:
675 v = line._x
676 asc = v[0] < v[-1]
677
678 idx = None
679 if not asc:
680 if v[len(v)-1] <= freq <= v[0]:
681 i = len(v)-1
682 while i>=0 and v[i] < freq:
683 idx = i
684 i-=1
685 else:
686 if v[0] <= freq <= v[len(v)-1]:
687 i = 0
688 while i<len(v) and v[i] < freq:
689 idx = i
690 i+=1
691 if idx is not None:
692 lower = idx - deltachan
693 upper = idx + deltachan
694 if lower < 0: lower = 0
695 if upper > len(v): upper = len(v)
696 s = slice(lower, upper)
697 y = line._y[s]
698 maxy = ma.maximum(y)
699 if isinstance( maxy, float):
700 maxys.append(maxy)
701 if len(maxys):
702 peak = max(maxys)
703 if peak > self._plotter.axes.get_ylim()[1]:
704 loc = 'bottom'
705 else:
706 continue
707 self._plotter.vline_with_label(freq, peak,
708 linecat.get_name(row),
709 location=loc, rotate=rotate)
710 self._plotter.show(hardrefresh=False)
711
712
713 def save(self, filename=None, orientation=None, dpi=None):
714 """
715 Save the plot to a file. The know formats are 'png', 'ps', 'eps'.
716 Parameters:
717 filename: The name of the output file. This is optional
718 and autodetects the image format from the file
719 suffix. If non filename is specified a file
720 called 'yyyymmdd_hhmmss.png' is created in the
721 current directory.
722 orientation: optional parameter for postscript only (not eps).
723 'landscape', 'portrait' or None (default) are valid.
724 If None is choosen for 'ps' output, the plot is
725 automatically oriented to fill the page.
726 dpi: The dpi of the output non-ps plot
727 """
728 self._plotter.save(filename,orientation,dpi)
729 return
730
731 @asaplog_post_dec
732 def set_mask(self, mask=None, selection=None, refresh=True):
733 """
734 Set a plotting mask for a specific polarization.
735 This is useful for masking out "noise" Pangle outside a source.
736 Parameters:
737 mask: a mask from scantable.create_mask
738 selection: the spectra to apply the mask to.
739 refresh: True (default) or False. If True, the plot is
740 replotted based on the new parameter setting(s).
741 Otherwise,the parameter(s) are set without replotting.
742 Example:
743 select = selector()
744 select.setpolstrings("Pangle")
745 plotter.set_mask(mymask, select)
746 """
747 if not self._data:
748 msg = "Can only set mask after a first call to plot()"
749 raise RuntimeError(msg)
750 if len(mask):
751 if isinstance(mask, list) or isinstance(mask, tuple):
752 self._usermask = array(mask)
753 else:
754 self._usermask = mask
755 if mask is None and selection is None:
756 self._usermask = []
757 self._maskselection = None
758 if isinstance(selection, selector):
759 self._maskselection = {'b': selection.get_beams(),
760 's': selection.get_scans(),
761 'i': selection.get_ifs(),
762 'p': selection.get_pols(),
763 't': [] }
764 else:
765 self._maskselection = None
766 if refresh: self.plot(self._data)
767
768 def _slice_indeces(self, data):
769 mn = self._minmaxx[0]
770 mx = self._minmaxx[1]
771 asc = data[0] < data[-1]
772 start=0
773 end = len(data)-1
774 inc = 1
775 if not asc:
776 start = len(data)-1
777 end = 0
778 inc = -1
779 # find min index
780 #while start > 0 and data[start] < mn:
781 # start+= inc
782 minind=start
783 for ind in xrange(start,end+inc,inc):
784 if data[ind] > mn: break
785 minind=ind
786 # find max index
787 #while end > 0 and data[end] > mx:
788 # end-=inc
789 #if end > 0: end +=1
790 maxind=end
791 for ind in xrange(end,start-inc,-inc):
792 if data[ind] < mx: break
793 maxind=ind
794 start=minind
795 end=maxind
796 if start > end:
797 return end,start+1
798 elif start < end:
799 return start,end+1
800 else:
801 return start,end
802
803 def _reset(self):
804 self._usermask = []
805 self._usermaskspectra = None
806 self.set_selection(None, False)
807
808 def _plot(self, scan):
809 savesel = scan.get_selection()
810 sel = savesel + self._selection
811 d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
812 'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME' }
813 order = [d0[self._panelling],d0[self._stacking]]
814 sel.set_order(order)
815 scan.set_selection(sel)
816 d = {'b': scan.getbeam, 's': scan.getscan,
817 'i': scan.getif, 'p': scan.getpol, 't': scan._gettime }
818
819 polmodes = dict(zip(self._selection.get_pols(),
820 self._selection.get_poltypes()))
821 # this returns either a tuple of numbers or a length (ncycles)
822 # convert this into lengths
823 n0,nstack0 = self._get_selected_n(scan)
824 if isinstance(n0, int): n = n0
825 else: n = len(n0)
826 if isinstance(nstack0, int): nstack = nstack0
827 else: nstack = len(nstack0)
828 maxpanel, maxstack = 16,16
829 if n > maxpanel or nstack > maxstack:
830 maxn = 0
831 if nstack > maxstack: maxn = maxstack
832 if n > maxpanel: maxn = maxpanel
833 msg ="Scan to be plotted contains more than %d selections.\n" \
834 "Selecting first %d selections..." % (maxn, maxn)
835 asaplog.push(msg)
836 asaplog.post('WARN')
837 n = min(n,maxpanel)
838 nstack = min(nstack,maxstack)
839 if n > 1:
840 ganged = rcParams['plotter.ganged']
841 if self._panelling == 'i':
842 ganged = False
843 if self._rows and self._cols:
844 n = min(n,self._rows*self._cols)
845 self._plotter.set_panels(rows=self._rows,cols=self._cols,
846# nplots=n,ganged=ganged)
847 nplots=n,layout=self._panellayout,ganged=ganged)
848 else:
849# self._plotter.set_panels(rows=n,cols=0,nplots=n,ganged=ganged)
850 self._plotter.set_panels(rows=n,cols=0,nplots=n,layout=self._panellayout,ganged=ganged)
851 else:
852# self._plotter.set_panels()
853 self._plotter.set_panels(layout=self._panellayout)
854 r=0
855 nr = scan.nrow()
856 a0,b0 = -1,-1
857 allxlim = []
858 allylim = []
859 newpanel=True
860 panelcount,stackcount = 0,0
861 while r < nr:
862 a = d[self._panelling](r)
863 b = d[self._stacking](r)
864 if a > a0 and panelcount < n:
865 if n > 1:
866 self._plotter.subplot(panelcount)
867 self._plotter.palette(0)
868 #title
869 xlab = self._abcissa and self._abcissa[panelcount] \
870 or scan._getabcissalabel()
871 ylab = self._ordinate and self._ordinate[panelcount] \
872 or scan._get_ordinate_label()
873 self._plotter.set_axes('xlabel', xlab)
874 self._plotter.set_axes('ylabel', ylab)
875 lbl = self._get_label(scan, r, self._panelling, self._title)
876 if isinstance(lbl, list) or isinstance(lbl, tuple):
877 if 0 <= panelcount < len(lbl):
878 lbl = lbl[panelcount]
879 else:
880 # get default label
881 lbl = self._get_label(scan, r, self._panelling, None)
882 self._plotter.set_axes('title',lbl)
883 newpanel = True
884 stackcount =0
885 panelcount += 1
886 if (b > b0 or newpanel) and stackcount < nstack:
887 y = []
888 if len(polmodes):
889 y = scan._getspectrum(r, polmodes[scan.getpol(r)])
890 else:
891 y = scan._getspectrum(r)
892 m = scan._getmask(r)
893 from numpy import logical_not, logical_and
894 if self._maskselection and len(self._usermask) == len(m):
895 if d[self._stacking](r) in self._maskselection[self._stacking]:
896 m = logical_and(m, self._usermask)
897 x = scan._getabcissa(r)
898 from numpy import ma, array
899 y = ma.masked_array(y,mask=logical_not(array(m,copy=False)))
900 if self._minmaxx is not None:
901 s,e = self._slice_indeces(x)
902 x = x[s:e]
903 y = y[s:e]
904 if len(x) > 1024 and rcParams['plotter.decimate']:
905 fac = len(x)/1024
906 x = x[::fac]
907 y = y[::fac]
908 llbl = self._get_label(scan, r, self._stacking, self._lmap)
909 if isinstance(llbl, list) or isinstance(llbl, tuple):
910 if 0 <= stackcount < len(llbl):
911 # use user label
912 llbl = llbl[stackcount]
913 else:
914 # get default label
915 llbl = self._get_label(scan, r, self._stacking, None)
916 self._plotter.set_line(label=llbl)
917 plotit = self._plotter.plot
918 if self._hist: plotit = self._plotter.hist
919 if len(x) > 0:
920 plotit(x,y)
921 xlim= self._minmaxx or [min(x),max(x)]
922 allxlim += xlim
923 ylim= self._minmaxy or [ma.minimum(y),ma.maximum(y)]
924 allylim += ylim
925 else:
926 xlim = self._minmaxx or []
927 allxlim += xlim
928 ylim= self._minmaxy or []
929 allylim += ylim
930 stackcount += 1
931 # last in colour stack -> autoscale x
932 if stackcount == nstack and len(allxlim) > 0:
933 allxlim.sort()
934 self._plotter.subplots[panelcount-1]['axes'].set_xlim([allxlim[0],allxlim[-1]])
935 # clear
936 allxlim =[]
937
938 newpanel = False
939 a0=a
940 b0=b
941 # ignore following rows
942 if (panelcount == n) and (stackcount == nstack):
943 # last panel -> autoscale y if ganged
944 if rcParams['plotter.ganged'] and len(allylim) > 0:
945 allylim.sort()
946 self._plotter.set_limits(ylim=[allylim[0],allylim[-1]])
947 break
948 r+=1 # next row
949 #reset the selector to the scantable's original
950 scan.set_selection(savesel)
951
952 #temporary switch-off for older matplotlib
953 #if self._fp is not None:
954 if self._fp is not None and getattr(self._plotter.figure,'findobj',False):
955 for o in self._plotter.figure.findobj(Text):
956 o.set_fontproperties(self._fp)
957
958 def set_selection(self, selection=None, refresh=True, **kw):
959 """
960 Parameters:
961 selection: a selector object (default unset the selection)
962 refresh: True (default) or False. If True, the plot is
963 replotted based on the new parameter setting(s).
964 Otherwise,the parameter(s) are set without replotting.
965 """
966 if selection is None:
967 # reset
968 if len(kw) == 0:
969 self._selection = selector()
970 else:
971 # try keywords
972 for k in kw:
973 if k not in selector.fields:
974 raise KeyError("Invalid selection key '%s', valid keys are %s" % (k, selector.fields))
975 self._selection = selector(**kw)
976 elif isinstance(selection, selector):
977 self._selection = selection
978 else:
979 raise TypeError("'selection' is not of type selector")
980
981 d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
982 'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME' }
983 order = [d0[self._panelling],d0[self._stacking]]
984 self._selection.set_order(order)
985 if refresh and self._data: self.plot(self._data)
986
987 def _get_selected_n(self, scan):
988 d1 = {'b': scan.getbeamnos, 's': scan.getscannos,
989 'i': scan.getifnos, 'p': scan.getpolnos, 't': scan.ncycle }
990 d2 = { 'b': self._selection.get_beams(),
991 's': self._selection.get_scans(),
992 'i': self._selection.get_ifs(),
993 'p': self._selection.get_pols(),
994 't': self._selection.get_cycles() }
995 n = d2[self._panelling] or d1[self._panelling]()
996 nstack = d2[self._stacking] or d1[self._stacking]()
997 return n,nstack
998
999 def _get_label(self, scan, row, mode, userlabel=None):
1000 if isinstance(userlabel, list) and len(userlabel) == 0:
1001 userlabel = " "
1002 pms = dict(zip(self._selection.get_pols(),self._selection.get_poltypes()))
1003 if len(pms):
1004 poleval = scan._getpollabel(scan.getpol(row),pms[scan.getpol(row)])
1005 else:
1006 poleval = scan._getpollabel(scan.getpol(row),scan.poltype())
1007 d = {'b': "Beam "+str(scan.getbeam(row)),
1008 #'s': scan._getsourcename(row),
1009 's': "Scan "+str(scan.getscan(row))+\
1010 " ("+str(scan._getsourcename(row))+")",
1011 'i': "IF"+str(scan.getif(row)),
1012 'p': poleval,
1013 't': str(scan.get_time(row)) }
1014 return userlabel or d[mode]
1015
1016 def plotazel(self, scan=None, outfile=None):
1017 #def plotazel(self):
1018 """
1019 plot azimuth and elevation versus time of a scantable
1020 """
1021 from matplotlib import pylab as PL
1022 from matplotlib.dates import DateFormatter, timezone
1023 from matplotlib.dates import HourLocator, MinuteLocator,SecondLocator, DayLocator
1024 from matplotlib.ticker import MultipleLocator
1025 from numpy import array, pi
1026 self._data = scan
1027 self._outfile = outfile
1028 dates = self._data.get_time(asdatetime=True)
1029 t = PL.date2num(dates)
1030 tz = timezone('UTC')
1031 PL.cla()
1032 PL.ioff()
1033 PL.clf()
1034 # Adjust subplot layouts
1035 if len(self._panellayout) !=6: self.set_panellayout(refresh=False)
1036 lef, bot, rig, top, wsp, hsp = self._panellayout
1037 PL.gcf().subplots_adjust(left=lef,bottom=bot,right=rig,top=top,
1038 wspace=wsp,hspace=hsp)
1039
1040 tdel = max(t) - min(t)
1041 ax = PL.subplot(2,1,1)
1042 el = array(self._data.get_elevation())*180./pi
1043 PL.ylabel('El [deg.]')
1044 dstr = dates[0].strftime('%Y/%m/%d')
1045 if tdel > 1.0:
1046 dstr2 = dates[len(dates)-1].strftime('%Y/%m/%d')
1047 dstr = dstr + " - " + dstr2
1048 majloc = DayLocator()
1049 minloc = HourLocator(range(0,23,12))
1050 timefmt = DateFormatter("%b%d")
1051 elif tdel > 24./60.:
1052 timefmt = DateFormatter('%H:%M')
1053 majloc = HourLocator()
1054 minloc = MinuteLocator(30)
1055 else:
1056 timefmt = DateFormatter('%H:%M')
1057 majloc = MinuteLocator(interval=5)
1058 minloc = SecondLocator(30)
1059
1060 PL.title(dstr)
1061 if tdel == 0.0:
1062 th = (t - PL.floor(t))*24.0
1063 PL.plot(th,el,'o',markersize=2, markerfacecolor='b', markeredgecolor='b')
1064 else:
1065 PL.plot_date(t,el,'o', markersize=2, markerfacecolor='b', markeredgecolor='b',tz=tz)
1066 #ax.grid(True)
1067 ax.xaxis.set_major_formatter(timefmt)
1068 ax.xaxis.set_major_locator(majloc)
1069 ax.xaxis.set_minor_locator(minloc)
1070 ax.yaxis.grid(True)
1071 yloc = MultipleLocator(30)
1072 ax.set_ylim(0,90)
1073 ax.yaxis.set_major_locator(yloc)
1074 if tdel > 1.0:
1075 labels = ax.get_xticklabels()
1076 # PL.setp(labels, fontsize=10, rotation=45)
1077 PL.setp(labels, fontsize=10)
1078
1079 # Az plot
1080 az = array(self._data.get_azimuth())*180./pi
1081 if min(az) < 0:
1082 for irow in range(len(az)):
1083 if az[irow] < 0: az[irow] += 360.0
1084
1085 ax2 = PL.subplot(2,1,2)
1086 #PL.xlabel('Time (UT [hour])')
1087 PL.ylabel('Az [deg.]')
1088 if tdel == 0.0:
1089 PL.plot(th,az,'o',markersize=2, markeredgecolor='b',markerfacecolor='b')
1090 else:
1091 PL.plot_date(t,az,'o', markersize=2,markeredgecolor='b',markerfacecolor='b',tz=tz)
1092 ax2.xaxis.set_major_formatter(timefmt)
1093 ax2.xaxis.set_major_locator(majloc)
1094 ax2.xaxis.set_minor_locator(minloc)
1095 #ax2.grid(True)
1096 ax2.set_ylim(0,360)
1097 ax2.yaxis.grid(True)
1098 #hfmt = DateFormatter('%H')
1099 #hloc = HourLocator()
1100 yloc = MultipleLocator(60)
1101 ax2.yaxis.set_major_locator(yloc)
1102 if tdel > 1.0:
1103 labels = ax2.get_xticklabels()
1104 PL.setp(labels, fontsize=10)
1105 PL.xlabel('Time (UT [day])')
1106 else:
1107 PL.xlabel('Time (UT [hour])')
1108
1109 PL.ion()
1110 PL.draw()
1111 if (self._outfile is not None):
1112 PL.savefig(self._outfile)
1113
1114 def plotpointing(self, scan=None, outfile=None):
1115 #def plotpointing(self):
1116 """
1117 plot telescope pointings
1118 """
1119 from matplotlib import pylab as PL
1120 from numpy import array, pi
1121 self._data = scan
1122 self._outfile = outfile
1123 dir = array(self._data.get_directionval()).transpose()
1124 ra = dir[0]*180./pi
1125 dec = dir[1]*180./pi
1126 PL.cla()
1127 #PL.ioff()
1128 PL.clf()
1129 # Adjust subplot layouts
1130 if len(self._panellayout) !=6: self.set_panellayout(refresh=False)
1131 lef, bot, rig, top, wsp, hsp = self._panellayout
1132 PL.gcf().subplots_adjust(left=lef,bottom=bot,right=rig,top=top,
1133 wspace=wsp,hspace=hsp)
1134 ax = PL.gca()
1135 #ax = PL.axes([0.1,0.1,0.8,0.8])
1136 #ax = PL.axes([0.1,0.1,0.8,0.8])
1137 ax.set_aspect('equal')
1138 PL.plot(ra, dec, 'b,')
1139 PL.xlabel('RA [deg.]')
1140 PL.ylabel('Declination [deg.]')
1141 PL.title('Telescope pointings')
1142 [xmin,xmax,ymin,ymax] = PL.axis()
1143 PL.axis([xmax,xmin,ymin,ymax])
1144 #PL.ion()
1145 PL.draw()
1146 if (self._outfile is not None):
1147 PL.savefig(self._outfile)
1148
1149 # plot total power data
1150 # plotting in time is not yet implemented..
1151 @asaplog_post_dec
1152 def plottp(self, scan=None, outfile=None):
1153 if self._plotter.is_dead:
1154 if hasattr(self._plotter.figmgr,'casabar'):
1155 del self._plotter.figmgr.casabar
1156 self._plotter = self._newplotter()
1157 self._plotter.figmgr.casabar=self._newcasabar()
1158 self._plotter.hold()
1159 self._plotter.clear()
1160 from asap import scantable
1161 if not self._data and not scan:
1162 msg = "Input is not a scantable"
1163 raise TypeError(msg)
1164 if isinstance(scan, scantable):
1165 if self._data is not None:
1166 if scan != self._data:
1167 self._data = scan
1168 # reset
1169 self._reset()
1170 else:
1171 self._data = scan
1172 self._reset()
1173 # ranges become invalid when abcissa changes?
1174 #if self._abcunit and self._abcunit != self._data.get_unit():
1175 # self._minmaxx = None
1176 # self._minmaxy = None
1177 # self._abcunit = self._data.get_unit()
1178 # self._datamask = None
1179
1180 # Adjust subplot layouts
1181 if len(self._panellayout) !=6: self.set_panellayout(refresh=False)
1182 lef, bot, rig, top, wsp, hsp = self._panellayout
1183 self._plotter.figure.subplots_adjust(
1184 left=lef,bottom=bot,right=rig,top=top,wspace=wsp,hspace=hsp)
1185 if self._plotter.figmgr.casabar: self._plotter.figmgr.casabar.disable_button()
1186 self._plottp(self._data)
1187 if self._minmaxy is not None:
1188 self._plotter.set_limits(ylim=self._minmaxy)
1189 self._plotter.release()
1190 self._plotter.tidy()
1191 self._plotter.show(hardrefresh=False)
1192 return
1193
1194 def _plottp(self,scan):
1195 """
1196 private method for plotting total power data
1197 """
1198 from numpy import ma, array, arange, logical_not
1199 r=0
1200 nr = scan.nrow()
1201 a0,b0 = -1,-1
1202 allxlim = []
1203 allylim = []
1204 y=[]
1205 self._plotter.set_panels()
1206 self._plotter.palette(0)
1207 #title
1208 #xlab = self._abcissa and self._abcissa[panelcount] \
1209 # or scan._getabcissalabel()
1210 #ylab = self._ordinate and self._ordinate[panelcount] \
1211 # or scan._get_ordinate_label()
1212 xlab = self._abcissa or 'row number' #or Time
1213 ylab = self._ordinate or scan._get_ordinate_label()
1214 self._plotter.set_axes('xlabel',xlab)
1215 self._plotter.set_axes('ylabel',ylab)
1216 lbl = self._get_label(scan, r, 's', self._title)
1217 if isinstance(lbl, list) or isinstance(lbl, tuple):
1218 # if 0 <= panelcount < len(lbl):
1219 # lbl = lbl[panelcount]
1220 # else:
1221 # get default label
1222 lbl = self._get_label(scan, r, self._panelling, None)
1223 self._plotter.set_axes('title',lbl)
1224 y=array(scan._get_column(scan._getspectrum,-1))
1225 m = array(scan._get_column(scan._getmask,-1))
1226 y = ma.masked_array(y,mask=logical_not(array(m,copy=False)))
1227 x = arange(len(y))
1228 # try to handle spectral data somewhat...
1229 l,m = y.shape
1230 if m > 1:
1231 y=y.mean(axis=1)
1232 plotit = self._plotter.plot
1233 llbl = self._get_label(scan, r, self._stacking, None)
1234 self._plotter.set_line(label=llbl)
1235 if len(x) > 0:
1236 plotit(x,y)
1237
1238
1239 # forwards to matplotlib.Figure.text
1240 def figtext(self, *args, **kwargs):
1241 """
1242 Add text to figure at location x,y (relative 0-1 coords).
1243 This method forwards *args and **kwargs to a Matplotlib method,
1244 matplotlib.Figure.text.
1245 See the method help for detailed information.
1246 """
1247 self._plotter.text(*args, **kwargs)
1248 # end matplotlib.Figure.text forwarding function
1249
1250
1251 # printing header information
1252 @asaplog_post_dec
1253 def print_header(self, plot=True, fontsize=9, logger=False, selstr='', extrastr=''):
1254 """
1255 print data (scantable) header on the plot and/or logger.
1256 Parameters:
1257 plot: whether or not print header info on the plot.
1258 fontsize: header font size (valid only plot=True)
1259 autoscale: whether or not autoscale the plot (valid only plot=True)
1260 logger: whether or not print header info on the logger.
1261 selstr: additional selection string (not verified)
1262 extrastr: additional string to print (not verified)
1263 """
1264 if not plot and not logger:
1265 return
1266 if not self._data:
1267 raise RuntimeError("No scantable has been set yet.")
1268 # Now header will be printed on plot and/or logger.
1269 # Get header information and format it.
1270 ssum=self._data.__str__()
1271 # Print Observation header to the upper-left corner of plot
1272 if plot:
1273 headstr=[ssum[ssum.find('Observer:'):ssum.find('Flux Unit:')]]
1274 headstr.append(ssum[ssum.find('Beams:'):ssum.find('Observer:')]
1275 +ssum[ssum.find('Rest Freqs:'):ssum.find('Abcissa:')])
1276 if extrastr != '': headstr[0]=extrastr+'\n'+headstr[0]
1277 #headstr[1]='Data File: '+(filestr or 'unknown')+'\n'+headstr[1]
1278 ssel='***Selections***\n'+(selstr+self._data.get_selection().__str__() or 'none')
1279 headstr.append(ssel)
1280 nstcol=len(headstr)
1281
1282 self._plotter.hold()
1283 for i in range(nstcol):
1284 self._plotter.figure.text(0.03+float(i)/nstcol,0.98,
1285 headstr[i],
1286 horizontalalignment='left',
1287 verticalalignment='top',
1288 fontsize=fontsize)
1289 import time
1290 self._plotter.figure.text(0.99,0.0,
1291 time.strftime("%a %d %b %Y %H:%M:%S %Z"),
1292 horizontalalignment='right',
1293 verticalalignment='bottom',fontsize=8)
1294 self._plotter.release()
1295 del headstr, ssel
1296 if logger:
1297 asaplog.push("----------------\n Plot Summary\n----------------")
1298 asaplog.push(extrastr)
1299 asaplog.push(ssum[ssum.find('Beams:'):])
1300 del ssum
Note: See TracBrowser for help on using the repository browser.