source: trunk/python/asapplotter.py@ 1901

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

Ticket #202: add offset keyword to asapplotter.set_range

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