source: trunk/python/asapplotter.py @ 2650

Last change on this file since 2650 was 2650, checked in by Malte Marquarding, 12 years ago

Issue #278: don't reset plotter on set_selection; use combined data and plotter selection to plot.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 73.5 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
8from matplotlib import _pylab_helpers
9
10import re
11
12def new_asaplot(visible=None,**kwargs):
13    """
14    Returns a new asaplot instance based on the backend settings.
15    """
16    if visible == None:
17        visible = rcParams['plotter.gui']
18
19    backend=matplotlib.get_backend()
20    if not visible:
21        from asap.asaplot import asaplot
22    elif backend == 'TkAgg':
23        from asap.asaplotgui import asaplotgui as asaplot
24    elif backend == 'Qt4Agg':
25        from asap.asaplotgui_qt4 import asaplotgui as asaplot
26    elif backend == 'GTkAgg':
27        from asap.asaplotgui_gtk import asaplotgui as asaplot
28    else:
29        from asap.asaplot import asaplot
30    return asaplot(**kwargs)
31
32class asapplotter:
33    """
34    The ASAP plotter.
35    By default the plotter is set up to plot polarisations
36    'colour stacked' and scantables across panels.
37
38    .. note::
39
40        Currenly it only plots 'spectra' not Tsys or
41        other variables.
42
43    """
44    def __init__(self, visible=None , **kwargs):
45        self._visible = rcParams['plotter.gui']
46        if visible is not None:
47            self._visible = visible
48        self._plotter = None
49        self._inikwg = kwargs
50
51        self._panelling = None
52        self._stacking = None
53        self.set_panelling()
54        self.set_stacking()
55        self._rows = None
56        self._cols = None
57        self._minmaxx = None
58        self._minmaxy = None
59        self._datamask = None
60        self._data = None
61        self._lmap = None
62        self._title = None
63        self._ordinate = None
64        self._abcissa = None
65        self._abcunit = None
66        self._usermask = []
67        self._maskselection = None
68        self._selection = selector()
69        self._hist = rcParams['plotter.histogram']
70        self._fp = FontProperties()
71        self._margins = self.set_margin(refresh=False)
72        self._offset = None
73        self._startrow = 0
74        self._ipanel = -1
75        self._panelrows = []
76        self._headtext={'string': None, 'textobj': None}
77        self._colormap = None
78        self._linestyles = None
79        self._legendloc = None
80
81    def _translate(self, instr):
82        keys = "s b i p t r".split()
83        if isinstance(instr, str):
84            for key in keys:
85                if instr.lower().startswith(key):
86                    return key
87        return None
88
89    @asaplog_post_dec
90    def _reload_plotter(self):
91        if self._plotter is not None:
92            #if not self._plotter.is_dead:
93            #    # clear lines and axes
94            #    try:
95            #        self._plotter.clear()
96            #    except: # Can't remove when already killed.
97            #        pass
98            if self.casabar_exists():
99                del self._plotter.figmgr.casabar
100            self._plotter.quit()
101            del self._plotter
102        asaplog.push('Loading new plotter')
103        self._plotter = new_asaplot(self._visible,**self._inikwg)
104        self._plotter.figmgr.casabar=self._new_custombar()
105        # just to make sure they're set
106        self._plotter.palette(color=0,colormap=self._colormap,
107                              linestyle=0,linestyles=self._linestyles)
108        self._plotter.legend(self._legendloc)
109
110    def _new_custombar(self):
111        backend=matplotlib.get_backend()
112        if not self._visible:
113            return None
114        elif backend == "TkAgg":
115            from asap.customgui_tkagg import CustomToolbarTkAgg
116            return CustomToolbarTkAgg(self)
117        elif backend == "Qt4Agg":
118            from asap.customgui_qt4agg import CustomToolbarQT4Agg
119            return CustomToolbarQT4Agg(self)
120        return None
121
122    def casabar_exists(self):
123        if not hasattr(self._plotter.figmgr,'casabar'):
124            return False
125        elif self._plotter.figmgr.casabar:
126            return True
127        return False
128
129    def _assert_plotter(self,action="status",errmsg=None):
130        """
131        Check plot window status. Returns True if plot window is alive.
132        Parameters
133            action:    An action to take if the plotter window is not alive.
134                       ['status'|'reload'|'halt']
135                       The action 'status' simply returns False if asaplot
136                       is not alive. When action='reload', plot window is
137                       reloaded and the method returns True. Finally, an
138                       error is raised when action='halt'.
139            errmsg:    An error (warning) message to send to the logger,
140                       when plot window is not alive.
141        """
142        isAlive = (self._plotter is not None) and self._plotter._alive()
143        # More tests
144        #if isAlive:
145        #    if self._plotter.figmgr:
146        #        figmgr = self._plotter.figmgr
147        #        figid = figmgr.num
148        #        # Make sure figid=0 is what asapplotter expects.
149        #        # It might be already destroied/overridden by matplotlib
150        #        # commands or other plotting methods using asaplot.
151        #        isAlive = _pylab_helpers.Gcf.has_fignum(figid) and \
152        #                  (figmgr == \
153        #                   _pylab_helpers.Gcf.get_fig_manager(figid))
154        #    else:
155        #        isAlive = False
156           
157        if isAlive:
158            return True
159        # Plotter is not alive.
160        haltmsg = "Plotter window has not yet been loaded or is closed."
161        if type(errmsg)==str and len(errmsg) > 0:
162            haltmsg = errmsg
163       
164        if action.upper().startswith("R"):
165            # reload plotter
166            self._reload_plotter()
167            return True
168        elif action.upper().startswith("H"):
169            # halt
170            asaplog.push(haltmsg)
171            asaplog.post("ERROR")
172            raise RuntimeError(haltmsg)
173        else:
174            if errmsg:
175                asaplog.push(errmsg)
176                asaplog.post("WARN")
177            return False
178
179
180    @asaplog_post_dec
181    def plot(self, scan=None):
182        """
183        Plot a scantable.
184        Parameters:
185            scan:   a scantable
186        Note:
187            If a scantable was specified in a previous call
188            to plot, no argument has to be given to 'replot'
189            NO checking is done that the abcissas of the scantable
190            are consistent e.g. all 'channel' or all 'velocity' etc.
191        """
192        if not self._data and not scan:
193            msg = "Input is not a scantable"
194            raise TypeError(msg)
195        self._startrow = 0
196        self._ipanel = -1
197        self._reset_header()
198        self._panelrows = []
199
200        self._assert_plotter(action="reload")
201        if self.casabar_exists():
202            self._plotter.figmgr.casabar.set_pagecounter(1)
203
204        self._plotter.hold()
205        #self._plotter.clear()
206        if scan:
207            self.set_data(scan, refresh=False)
208        self._plotter.palette(color=0,colormap=self._colormap,
209                              linestyle=0,linestyles=self._linestyles)
210        self._plotter.legend(self._legendloc)
211        self._plot(self._data)
212        if self._minmaxy is not None:
213            self._plotter.set_limits(ylim=self._minmaxy)
214        if self.casabar_exists(): self._plotter.figmgr.casabar.enable_button()
215        self._plotter.release()
216        self._plotter.tidy()
217        self._plotter.show(hardrefresh=False)
218        return
219
220    def gca(self):
221        errmsg = "No axis to retun. Need to plot first."
222        if not self._assert_plotter(action="status",errmsg=errmsg):
223            return None
224        return self._plotter.figure.gca()
225
226    def refresh(self):
227        """Do a soft refresh"""
228        errmsg = "No figure to re-plot. Need to plot first."
229        self._assert_plotter(action="halt",errmsg=errmsg)
230
231        self._plotter.figure.show()
232
233    def create_mask(self, nwin=1, panel=0, color=None):
234        """
235        Interactively define a mask. It retruns a mask that is equivalent to
236        the one created manually with scantable.create_mask.
237        Parameters:
238            nwin:       The number of mask windows to create interactively
239                        default is 1.
240            panel:      Which panel to use for mask selection. This is useful
241                        if different IFs are spread over panels (default 0)
242        """
243        ## this method relies on already plotted figure
244        if not self._assert_plotter(action="status") or (self._data is None):
245            msg = "Cannot create mask interactively on plot. Can only create mask after plotting."
246            asaplog.push( msg )
247            asaplog.post( "ERROR" )
248            return []
249        outmask = []
250        self._plotter.subplot(panel)
251        xmin, xmax = self._plotter.axes.get_xlim()
252        marg = 0.05*(xmax-xmin)
253        self._plotter.axes.set_xlim(xmin-marg, xmax+marg)
254        self.refresh()
255
256        def cleanup(lines=False, texts=False, refresh=False):
257            if lines:
258                del self._plotter.axes.lines[-1]
259            if texts:
260                del self._plotter.axes.texts[-1]
261            if refresh:
262                self.refresh()
263
264        for w in xrange(nwin):
265            wpos = []
266            self.text(0.05,1.0, "Add start boundary",
267                      coords="relative", fontsize=10)
268            point = self._plotter.get_point()
269            cleanup(texts=True)
270            if point is None:
271                continue
272            wpos.append(point[0])
273            self.axvline(wpos[0], color=color)
274            self.text(0.05,1.0, "Add end boundary", coords="relative", fontsize=10)
275            point = self._plotter.get_point()
276            cleanup(texts=True, lines=True)
277            if point is None:
278                self.refresh()
279                continue
280            wpos.append(point[0])
281            self.axvspan(wpos[0], wpos[1], alpha=0.1,
282                         edgecolor=color, facecolor=color)
283            ymin, ymax = self._plotter.axes.get_ylim()
284            outmask.append(wpos)
285
286        self._plotter.axes.set_xlim(xmin, xmax)
287        self.refresh()
288        if len(outmask) > 0:
289            return self._data.create_mask(*outmask)
290        return []
291
292    # forwards to matplotlib axes
293    def text(self, *args, **kwargs):
294        self._assert_plotter(action="reload")
295        if kwargs.has_key("interactive"):
296            if kwargs.pop("interactive"):
297                pos = self._plotter.get_point()
298                args = tuple(pos)+args
299        self._axes_callback("text", *args, **kwargs)
300
301    text.__doc__ = matplotlib.axes.Axes.text.__doc__
302
303    def arrow(self, *args, **kwargs):
304        self._assert_plotter(action="reload")
305        if kwargs.has_key("interactive"):
306            if kwargs.pop("interactive"):
307                pos = self._plotter.get_region()
308                dpos = (pos[0][0], pos[0][1],
309                        pos[1][0]-pos[0][0],
310                        pos[1][1] - pos[0][1])
311                args = dpos + args
312        self._axes_callback("arrow", *args, **kwargs)
313
314    arrow.__doc__ = matplotlib.axes.Axes.arrow.__doc__
315
316    def annotate(self, text, xy=None, xytext=None, **kwargs):
317        self._assert_plotter(action="reload")
318        if kwargs.has_key("interactive"):
319            if kwargs.pop("interactive"):
320                xy = self._plotter.get_point()
321                xytext = self._plotter.get_point()
322        if not kwargs.has_key("arrowprops"):
323            kwargs["arrowprops"] = dict(arrowstyle="->")
324        self._axes_callback("annotate", text, xy, xytext, **kwargs)
325
326    annotate.__doc__ = matplotlib.axes.Axes.annotate.__doc__
327
328    def axvline(self, *args, **kwargs):
329        self._assert_plotter(action="reload")
330        if kwargs.has_key("interactive"):
331            if kwargs.pop("interactive"):
332                pos = self._plotter.get_point()
333                args = (pos[0],)+args
334        self._axes_callback("axvline", *args, **kwargs)
335
336    axvline.__doc__ = matplotlib.axes.Axes.axvline.__doc__
337
338    def axhline(self, *args, **kwargs):
339        self._assert_plotter(action="reload")
340        if kwargs.has_key("interactive"):
341            if kwargs.pop("interactive"):
342                pos = self._plotter.get_point()
343                args = (pos[1],)+args
344        self._axes_callback("axhline", *args, **kwargs)
345
346    axhline.__doc__ = matplotlib.axes.Axes.axhline.__doc__
347
348    def axvspan(self, *args, **kwargs):
349        self._assert_plotter(action="reload")
350        if kwargs.has_key("interactive"):
351            if kwargs.pop("interactive"):
352                pos = self._plotter.get_region()
353                dpos = (pos[0][0], pos[1][0])
354                args = dpos + args
355        self._axes_callback("axvspan", *args, **kwargs)
356        # hack to preventy mpl from redrawing the patch
357        # it seem to convert the patch into lines on every draw.
358        # This doesn't happen in a test script???
359        #del self._plotter.axes.patches[-1]
360
361    axvspan.__doc__ = matplotlib.axes.Axes.axvspan.__doc__
362
363    def axhspan(self, *args, **kwargs):
364        self._assert_plotter(action="reload")
365        if kwargs.has_key("interactive"):
366            if kwargs.pop("interactive"):
367                pos = self._plotter.get_region()
368                dpos = (pos[0][1], pos[1][1])
369                args = dpos + args
370        self._axes_callback("axhspan", *args, **kwargs)
371        # hack to preventy mpl from redrawing the patch
372        # it seem to convert the patch into lines on every draw.
373        # This doesn't happen in a test script???
374        #del self._plotter.axes.patches[-1]
375
376    axhspan.__doc__ = matplotlib.axes.Axes.axhspan.__doc__
377
378    def _axes_callback(self, axesfunc, *args, **kwargs):
379        self._assert_plotter(action="reload")
380        panel = 0
381        if kwargs.has_key("panel"):
382            panel = kwargs.pop("panel")
383        coords = None
384        if kwargs.has_key("coords"):
385            coords = kwargs.pop("coords")
386            if coords.lower() == 'world':
387                kwargs["transform"] = self._plotter.axes.transData
388            elif coords.lower() == 'relative':
389                kwargs["transform"] = self._plotter.axes.transAxes
390        self._plotter.subplot(panel)
391        self._plotter.axes.set_autoscale_on(False)
392        getattr(self._plotter.axes, axesfunc)(*args, **kwargs)
393        self._plotter.show(False)
394        self._plotter.axes.set_autoscale_on(True)
395    # end matplotlib.axes fowarding functions
396
397    @asaplog_post_dec
398    def set_data(self, scan, refresh=True):
399        """
400        Set a scantable to plot.
401        Parameters:
402            scan:      a scantable
403            refresh:   True (default) or False. If True, the plot is
404                       replotted based on the new parameter setting(s).
405                       Otherwise,the parameter(s) are set without replotting.
406        Note:
407           The user specified masks and data selections will be reset
408           if a new scantable is set. This method should be called before
409           setting data selections (set_selection) and/or masks (set_mask).
410        """
411        from asap import scantable
412        if isinstance(scan, scantable):
413            if (self._data is not None) and (scan != self._data):
414                del self._data
415                msg = "A new scantable is set to the plotter. "\
416                      "The masks and data selections are reset."
417                asaplog.push( msg )
418            self._data = scan
419            # reset
420            self._reset()
421        else:
422            msg = "Input is not a scantable"
423            raise TypeError(msg)
424
425        # ranges become invalid when unit changes
426        if self._abcunit and self._abcunit != self._data.get_unit():
427            self._minmaxx = None
428            self._minmaxy = None
429            self._abcunit = self._data.get_unit()
430            self._datamask = None
431        if refresh: self.plot()
432
433    @asaplog_post_dec
434    def set_mode(self, stacking=None, panelling=None, refresh=True):
435        """
436        Set the plots look and feel, i.e. what you want to see on the plot.
437        Parameters:
438            stacking:     tell the plotter which variable to plot
439                          as line colour overlays (default 'pol')
440            panelling:    tell the plotter which variable to plot
441                          across multiple panels (default 'scan'
442            refresh:      True (default) or False. If True, the plot is
443                          replotted based on the new parameter setting(s).
444                          Otherwise,the parameter(s) are set without replotting.
445        Note:
446            Valid modes are:
447                 'beam' 'Beam' 'b':     Beams
448                 'if' 'IF' 'i':         IFs
449                 'pol' 'Pol' 'p':       Polarisations
450                 'scan' 'Scan' 's':     Scans
451                 'time' 'Time' 't':     Times
452                 'row' 'Row' 'r':       Rows
453            When either 'stacking' or 'panelling' is set to 'row',
454            the other parameter setting is ignored.
455        """
456        msg = "Invalid mode"
457        if not self.set_panelling(panelling) or \
458               not self.set_stacking(stacking):
459            raise TypeError(msg)
460        #if self._panelling == 'r':
461        #    self._stacking = '_r'
462        #if self._stacking == 'r':
463        #    self._panelling = '_r'
464        if refresh and self._data: self.plot(self._data)
465        return
466
467    def set_panelling(self, what=None):
468        """Set the 'panelling' mode i.e. which type of spectra should be
469        spread across different panels.
470        """
471
472        mode = what
473        if mode is None:
474             mode = rcParams['plotter.panelling']
475        md = self._translate(mode)
476        if md:
477            self._panelling = md
478            self._title = None
479            #if md == 'r':
480            #    self._stacking = '_r'
481            # you need to reset counters for multi page plotting
482            self._reset_counters()
483            return True
484        return False
485
486    def set_layout(self,rows=None,cols=None,refresh=True):
487        """
488        Set the multi-panel layout, i.e. how many rows and columns plots
489        are visible.
490        Parameters:
491             rows:   The number of rows of plots
492             cols:   The number of columns of plots
493             refresh:  True (default) or False. If True, the plot is
494                       replotted based on the new parameter setting(s).
495                       Otherwise,the parameter(s) are set without replotting.
496        Note:
497             If no argument is given, the potter reverts to its auto-plot
498             behaviour.
499        """
500        self._rows = rows
501        self._cols = cols
502        if refresh and self._data: self.plot(self._data)
503        return
504
505    def set_stacking(self, what=None):
506        """Set the 'stacking' mode i.e. which type of spectra should be
507        overlayed.
508        """
509        mode = what
510        if mode is None:
511             mode = rcParams['plotter.stacking']
512        md = self._translate(mode)
513        if md:
514            self._stacking = md
515            self._lmap = None
516            #if md == 'r':
517            #    self._panelling = '_r'
518            # you need to reset counters for multi page plotting
519            self._reset_counters()
520            return True
521        return False
522
523    def _reset_counters(self):
524        self._startrow = 0
525        self._ipanel = -1
526        self._panelrows = []
527
528    def set_range(self,xstart=None,xend=None,ystart=None,yend=None,refresh=True, offset=None):
529        """
530        Set the range of interest on the abcissa of the plot
531        Parameters:
532            [x,y]start,[x,y]end:  The start and end points of the 'zoom' window
533            refresh:  True (default) or False. If True, the plot is
534                      replotted based on the new parameter setting(s).
535                      Otherwise,the parameter(s) are set without replotting.
536            offset:   shift the abcissa by the given amount. The abcissa label will
537                      have '(relative)' appended to it.
538        Note:
539            These become non-sensical when the unit changes.
540            use plotter.set_range() without parameters to reset
541
542        """
543        self._offset = offset
544        if xstart is None and xend is None:
545            self._minmaxx = None
546        else:
547            self._minmaxx = [xstart,xend]
548        if ystart is None and yend is None:
549            self._minmaxy = None
550        else:
551            self._minmaxy = [ystart,yend]
552        if refresh and self._data: self.plot(self._data)
553        return
554
555    def set_legend(self, mp=None, fontsize = None, mode = 0, refresh=True):
556        """
557        Specify a mapping for the legend instead of using the default
558        indices:
559        Parameters:
560            mp:        a list of 'strings'. This should have the same length
561                       as the number of elements on the legend and then maps
562                       to the indeces in order. It is possible to uses latex
563                       math expression. These have to be enclosed in r'',
564                       e.g. r'$x^{2}$'
565            fontsize:  The font size of the label (default None)
566            mode:      where to display the legend
567                       Any other value for loc else disables the legend:
568                        0: auto
569                        1: upper right
570                        2: upper left
571                        3: lower left
572                        4: lower right
573                        5: right
574                        6: center left
575                        7: center right
576                        8: lower center
577                        9: upper center
578                        10: center
579            refresh:    True (default) or False. If True, the plot is
580                        replotted based on the new parameter setting(s).
581                        Otherwise,the parameter(s) are set without replotting.
582
583        Example:
584             If the data has two IFs/rest frequencies with index 0 and 1
585             for CO and SiO:
586             plotter.set_stacking('i')
587             plotter.set_legend(['CO','SiO'])
588             plotter.plot()
589             plotter.set_legend([r'$^{12}CO$', r'SiO'])
590        """
591        self._lmap = mp
592        #self._plotter.legend(mode)
593        self._legendloc = mode
594        if isinstance(fontsize, int):
595            from matplotlib import rc as rcp
596            rcp('legend', fontsize=fontsize)
597        if refresh and self._data: self.plot(self._data)
598        return
599
600    def set_title(self, title=None, fontsize=None, refresh=True):
601        """
602        Set the title of sub-plots. If multiple sub-plots are plotted,
603        multiple titles have to be specified.
604        Parameters:
605            title:      a list of titles of sub-plots.
606            fontsize:   a font size of titles (integer)
607            refresh:    True (default) or False. If True, the plot is
608                        replotted based on the new parameter setting(s).
609                        Otherwise,the parameter(s) are set without replotting.
610        Example:
611             # two panels are visible on the plotter
612             plotter.set_title(['First Panel','Second Panel'])
613        """
614        self._title = title
615        if isinstance(fontsize, int):
616            from matplotlib import rc as rcp
617            rcp('axes', titlesize=fontsize)
618        if refresh and self._data: self.plot(self._data)
619        return
620
621    def set_ordinate(self, ordinate=None, fontsize=None, refresh=True):
622        """
623        Set the y-axis label of the plot. If multiple panels are plotted,
624        multiple labels have to be specified.
625        Parameters:
626            ordinate:    a list of ordinate labels. None (default) let
627                         data determine the labels
628            fontsize:    a font size of vertical axis labels (integer)
629            refresh:     True (default) or False. If True, the plot is
630                         replotted based on the new parameter setting(s).
631                         Otherwise,the parameter(s) are set without replotting.
632        Example:
633             # two panels are visible on the plotter
634             plotter.set_ordinate(['First Y-Axis','Second Y-Axis'])
635        """
636        self._ordinate = ordinate
637        if isinstance(fontsize, int):
638            from matplotlib import rc as rcp
639            rcp('axes', labelsize=fontsize)
640            rcp('ytick', labelsize=fontsize)
641        if refresh and self._data: self.plot(self._data)
642        return
643
644    def set_abcissa(self, abcissa=None, fontsize=None, refresh=True):
645        """
646        Set the x-axis label of the plot. If multiple panels are plotted,
647        multiple labels have to be specified.
648        Parameters:
649            abcissa:     a list of abcissa labels. None (default) let
650                         data determine the labels
651            fontsize:    a font size of horizontal axis labels (integer)
652            refresh:     True (default) or False. If True, the plot is
653                         replotted based on the new parameter setting(s).
654                         Otherwise,the parameter(s) are set without replotting.
655        Example:
656             # two panels are visible on the plotter
657             plotter.set_ordinate(['First X-Axis','Second X-Axis'])
658        """
659        self._abcissa = abcissa
660        if isinstance(fontsize, int):
661            from matplotlib import rc as rcp
662            rcp('axes', labelsize=fontsize)
663            rcp('xtick', labelsize=fontsize)
664        if refresh and self._data: self.plot(self._data)
665        return
666
667    def set_colors(self, colmap, refresh=True):
668        """
669        Set the colours to be used. The plotter will cycle through
670        these colours when lines are overlaid (stacking mode).
671        Parameters:
672            colmap:     a list of colour names
673            refresh:    True (default) or False. If True, the plot is
674                        replotted based on the new parameter setting(s).
675                        Otherwise,the parameter(s) are set without replotting.
676        Example:
677             plotter.set_colors('red green blue')
678             # If for example four lines are overlaid e.g I Q U V
679             # 'I' will be 'red', 'Q' will be 'green', U will be 'blue'
680             # and 'V' will be 'red' again.
681        """
682        #if isinstance(colmap,str):
683        #    colmap = colmap.split()
684        #self._plotter.palette(0, colormap=colmap)
685        self._colormap = colmap
686        if refresh and self._data: self.plot(self._data)
687
688    # alias for english speakers
689    set_colours = set_colors
690
691    def set_histogram(self, hist=True, linewidth=None, refresh=True):
692        """
693        Enable/Disable histogram-like plotting.
694        Parameters:
695            hist:        True (default) or False. The fisrt default
696                         is taken from the .asaprc setting
697                         plotter.histogram
698            linewidth:   a line width
699            refresh:     True (default) or False. If True, the plot is
700                         replotted based on the new parameter setting(s).
701                         Otherwise,the parameter(s) are set without replotting.
702        """
703        self._hist = hist
704        if isinstance(linewidth, float) or isinstance(linewidth, int):
705            from matplotlib import rc as rcp
706            rcp('lines', linewidth=linewidth)
707        if refresh and self._data: self.plot(self._data)
708
709    def set_linestyles(self, linestyles=None, linewidth=None, refresh=True):
710        """
711        Set the linestyles to be used. The plotter will cycle through
712        these linestyles when lines are overlaid (stacking mode) AND
713        only one color has been set.
714        Parameters:
715            linestyles:      a list of linestyles to use.
716                             'line', 'dashed', 'dotted', 'dashdot',
717                             'dashdotdot' and 'dashdashdot' are
718                             possible
719            linewidth:       a line width
720            refresh:         True (default) or False. If True, the plot is
721                             replotted based on the new parameter setting(s).
722                             Otherwise,the parameter(s) are set without replotting.
723        Example:
724             plotter.set_colors('black')
725             plotter.set_linestyles('line dashed dotted dashdot')
726             # If for example four lines are overlaid e.g I Q U V
727             # 'I' will be 'solid', 'Q' will be 'dashed',
728             # U will be 'dotted' and 'V' will be 'dashdot'.
729        """
730        #if isinstance(linestyles,str):
731        #    linestyles = linestyles.split()
732        #self._plotter.palette(color=0,linestyle=0,linestyles=linestyles)
733        self._linestyles = linestyles
734        if isinstance(linewidth, float) or isinstance(linewidth, int):
735            from matplotlib import rc as rcp
736            rcp('lines', linewidth=linewidth)
737        if refresh and self._data: self.plot(self._data)
738
739    def set_font(self, refresh=True,**kwargs):
740        """
741        Set font properties.
742        Parameters:
743            family:    one of 'sans-serif', 'serif', 'cursive', 'fantasy', 'monospace'
744            style:     one of 'normal' (or 'roman'), 'italic'  or 'oblique'
745            weight:    one of 'normal or 'bold'
746            size:      the 'general' font size, individual elements can be adjusted
747                       seperately
748            refresh:   True (default) or False. If True, the plot is
749                       replotted based on the new parameter setting(s).
750                       Otherwise,the parameter(s) are set without replotting.
751        """
752        from matplotlib import rc as rcp
753        fdict = {}
754        for k,v in kwargs.iteritems():
755            if v:
756                fdict[k] = v
757        self._fp = FontProperties(**fdict)
758        if refresh and self._data: self.plot(self._data)
759
760    def set_margin(self,margin=[],refresh=True):
761        """
762        Set margins between subplots and plot edges.
763        Parameters:
764            margin:   a list of margins in figure coordinate (0-1),
765                      i.e., fraction of the figure width or height.
766                      The order of elements should be:
767                      [left, bottom, right, top, horizontal space btw panels,
768                      vertical space btw panels].
769            refresh:  True (default) or False. If True, the plot is
770                      replotted based on the new parameter setting(s).
771                      Otherwise,the parameter(s) are set without replotting.
772        Note
773        * When margin is not specified, the values are reset to the defaults
774          of matplotlib.
775        * If any element is set to be None, the current value is adopted.
776        """
777        if margin == []: self._margins=self._reset_margin()
778        else:
779            self._margins=[None]*6
780            self._margins[0:len(margin)]=margin
781        #print "panel margin set to ",self._margins
782        if refresh and self._data: self.plot(self._data)
783
784    def _reset_margin(self):
785        ks=map(lambda x: 'figure.subplot.'+x,
786               ['left','bottom','right','top','hspace','wspace'])
787        return map(matplotlib.rcParams.get,ks)
788
789    def plot_lines(self, linecat=None, doppler=0.0, deltachan=10, rotate=90.0,
790                   location=None):
791        """
792        Plot a line catalog.
793        Parameters:
794            linecat:      the linecatalog to plot
795            doppler:      the velocity shift to apply to the frequencies
796            deltachan:    the number of channels to include each side of the
797                          line to determine a local maximum/minimum
798            rotate:       the rotation (in degrees) for the text label (default 90.0)
799            location:     the location of the line annotation from the 'top',
800                          'bottom' or alternate (None - the default)
801        Notes:
802        If the spectrum is flagged no line will be drawn in that location.
803        """
804        errmsg = "Cannot plot spectral lines. Need to plot scantable first."
805        self._assert_plotter(action="halt",errmsg=errmsg)
806        if not self._data:
807            raise RuntimeError("No scantable has been plotted yet.")
808        from asap._asap import linecatalog
809        if not isinstance(linecat, linecatalog):
810            raise ValueError("'linecat' isn't of type linecatalog.")
811        if not self._data.get_unit().endswith("Hz"):
812            raise RuntimeError("Can only overlay linecatalogs when data is in frequency.")
813        from numpy import ma
814        for j in range(len(self._plotter.subplots)):
815            self._plotter.subplot(j)
816            lims = self._plotter.axes.get_xlim()
817            for row in range(linecat.nrow()):
818                # get_frequency returns MHz
819                base = { "GHz": 1000.0, "MHz": 1.0, "Hz": 1.0e-6 }
820                restf = linecat.get_frequency(row)/base[self._data.get_unit()]
821                c = 299792.458
822                freq = restf*(1.0-doppler/c)
823                if lims[0] < freq < lims[1]:
824                    if location is None:
825                        loc = 'bottom'
826                        if row%2: loc='top'
827                    else: loc = location
828                    maxys = []
829                    for line in self._plotter.axes.lines:
830                        v = line._x
831                        asc = v[0] < v[-1]
832
833                        idx = None
834                        if not asc:
835                            if v[len(v)-1] <= freq <= v[0]:
836                                i = len(v)-1
837                                while i>=0 and v[i] < freq:
838                                    idx = i
839                                    i-=1
840                        else:
841                           if v[0] <= freq <= v[len(v)-1]:
842                                i = 0
843                                while  i<len(v) and v[i] < freq:
844                                    idx = i
845                                    i+=1
846                        if idx is not None:
847                            lower = idx - deltachan
848                            upper = idx + deltachan
849                            if lower < 0: lower = 0
850                            if upper > len(v): upper = len(v)
851                            s = slice(lower, upper)
852                            y = line._y[s]
853                            maxy = ma.maximum(y)
854                            if isinstance( maxy, float):
855                                maxys.append(maxy)
856                    if len(maxys):
857                        peak = max(maxys)
858                        if peak > self._plotter.axes.get_ylim()[1]:
859                            loc = 'bottom'
860                    else:
861                        continue
862                    self._plotter.vline_with_label(freq, peak,
863                                                   linecat.get_name(row),
864                                                   location=loc, rotate=rotate)
865        self._plotter.show(hardrefresh=False)
866
867
868    def save(self, filename=None, orientation=None, dpi=None):
869        """
870        Save the plot to a file. The known formats are 'png', 'ps', 'eps'.
871        Parameters:
872             filename:    The name of the output file. This is optional
873                          and autodetects the image format from the file
874                          suffix. If non filename is specified a file
875                          called 'yyyymmdd_hhmmss.png' is created in the
876                          current directory.
877             orientation: optional parameter for postscript only (not eps).
878                          'landscape', 'portrait' or None (default) are valid.
879                          If None is choosen for 'ps' output, the plot is
880                          automatically oriented to fill the page.
881             dpi:         The dpi of the output non-ps plot
882        """
883        errmsg = "Cannot save figure. Need to plot first."
884        self._assert_plotter(action="halt",errmsg=errmsg)
885       
886        self._plotter.save(filename,orientation,dpi)
887        return
888
889    @asaplog_post_dec
890    def set_mask(self, mask=None, selection=None, refresh=True):
891        """
892        Set a plotting mask for a specific polarization.
893        This is useful for masking out 'noise' Pangle outside a source.
894        Parameters:
895             mask:           a mask from scantable.create_mask
896             selection:      the spectra to apply the mask to.
897             refresh:        True (default) or False. If True, the plot is
898                             replotted based on the new parameter setting(s).
899                             Otherwise,the parameter(s) are set without replotting.
900        Example:
901             select = selector()
902             select.setpolstrings('Pangle')
903             plotter.set_mask(mymask, select)
904        """
905        if not self._data:
906            msg = "Can only set mask after a first call to plot()"
907            raise RuntimeError(msg)
908        if len(mask):
909            if isinstance(mask, list) or isinstance(mask, tuple):
910                self._usermask = array(mask)
911            else:
912                self._usermask = mask
913        if mask is None and selection is None:
914            self._usermask = []
915            self._maskselection = None
916        if isinstance(selection, selector):
917            self._maskselection = {'b': selection.get_beams(),
918                                   's': selection.get_scans(),
919                                   'i': selection.get_ifs(),
920                                   'p': selection.get_pols(),
921                                   't': [] }
922        else:
923            self._maskselection = None
924        if refresh: self.plot(self._data)
925
926    def _slice_indeces(self, data):
927        mn = self._minmaxx[0]
928        mx = self._minmaxx[1]
929        asc = data[0] < data[-1]
930        start=0
931        end = len(data)-1
932        inc = 1
933        if not asc:
934            start = len(data)-1
935            end = 0
936            inc = -1
937        # find min index
938        #while start > 0 and data[start] < mn:
939        #    start+= inc
940        minind=start
941        for ind in xrange(start,end+inc,inc):
942            if data[ind] > mn: break
943            minind=ind
944        # find max index
945        #while end > 0 and data[end] > mx:
946        #    end-=inc
947        #if end > 0: end +=1
948        maxind=end
949        for ind in xrange(end,start-inc,-inc):
950            if data[ind] < mx: break
951            maxind=ind
952        start=minind
953        end=maxind
954        if start > end:
955            return end,start+1
956        elif start < end:
957            return start,end+1
958        else:
959            return start,end
960
961    def _reset(self):
962        self._usermask = []
963        self._usermaskspectra = None
964        self._offset = None
965        self.set_selection(None, False)
966        self._reset_header()
967
968    def _reset_header(self):
969        self._headtext={'string': None, 'textobj': None}
970
971    def _plot(self, scan):
972        savesel = scan.get_selection()
973        sel = savesel +  self._selection
974        order = self._get_sortstring([self._panelling,self._stacking])
975        if order:
976            sel.set_order(order)
977        scan.set_selection(sel)
978        d = {'b': scan.getbeam, 's': scan.getscan,
979             'i': scan.getif, 'p': scan.getpol, 't': scan.get_time,
980             'r': int}#, '_r': int}
981
982        polmodes = dict(zip(sel.get_pols(), sel.get_poltypes()))
983        # this returns either a tuple of numbers or a length  (ncycles)
984        # convert this into lengths
985        n0,nstack0 = self._get_selected_n(scan)
986        if isinstance(n0, int): n = n0
987        else: n = len(n0)
988        if isinstance(nstack0, int): nstack = nstack0
989        else: nstack = len(nstack0)
990        # In case of row stacking
991        rowstack = False
992        titlemode = self._panelling
993        if self._stacking == "r" and self._panelling != "r":
994            rowstack = True
995            titlemode = '_r'
996        nptot = n
997        maxpanel, maxstack = 16,16
998        if nstack > maxstack:
999            msg ="Scan to be overlayed contains more than %d selections.\n" \
1000                  "Selecting first %d selections..." % (maxstack, maxstack)
1001            asaplog.push(msg)
1002            asaplog.post('WARN')
1003            nstack = min(nstack,maxstack)
1004        #n = min(n-self._ipanel-1,maxpanel)
1005        n = n-self._ipanel-1
1006
1007        ganged = False
1008        if n > 1:
1009            ganged = rcParams['plotter.ganged']
1010            if self._panelling == 'i':
1011                ganged = False
1012            if self._rows and self._cols:
1013                n = min(n,self._rows*self._cols)
1014                self._plotter.set_panels(rows=self._rows,cols=self._cols,
1015                                         nplots=n,margin=self._margins,
1016                                         ganged=ganged)
1017            else:
1018                n = min(n,maxpanel)
1019                self._plotter.set_panels(rows=n,cols=0,nplots=n,
1020                                         margin=self._margins,ganged=ganged)
1021        else:
1022            self._plotter.set_panels(margin=self._margins)
1023        #r = 0
1024        r = self._startrow
1025        nr = scan.nrow()
1026        a0,b0 = -1,-1
1027        allxlim = []
1028        allylim = []
1029        #newpanel=True
1030        newpanel=False
1031        panelcount,stackcount = 0,0
1032        # If this is not the first page
1033        if r > 0:
1034            # panelling value of the prev page
1035            a0 = d[self._panelling](r-1)
1036            # set the initial stackcount large not to plot
1037            # the start row automatically
1038            stackcount = nstack
1039
1040        while r < nr:
1041            a = d[self._panelling](r)
1042            b = d[self._stacking](r)
1043            if a > a0 and panelcount < n:
1044                if n > 1:
1045                    self._plotter.subplot(panelcount)
1046                self._plotter.palette(0)
1047                #title
1048                xlab = self._abcissa and self._abcissa[panelcount] \
1049                       or scan._getabcissalabel()
1050                if self._offset and not self._abcissa:
1051                    xlab += " (relative)"
1052                ylab = self._ordinate and self._ordinate[panelcount] \
1053                       or scan._get_ordinate_label()
1054                self._plotter.set_axes('xlabel', xlab)
1055                self._plotter.set_axes('ylabel', ylab)
1056                #lbl = self._get_label(scan, r, self._panelling, self._title)
1057                lbl = self._get_label(scan, r, titlemode, self._title)
1058                if isinstance(lbl, list) or isinstance(lbl, tuple):
1059                    if 0 <= panelcount < len(lbl):
1060                        lbl = lbl[panelcount]
1061                    else:
1062                        # get default label
1063                        #lbl = self._get_label(scan, r, self._panelling, None)
1064                        lbl = self._get_label(scan, r, titlemode, None)
1065                self._plotter.set_axes('title',lbl)
1066                newpanel = True
1067                stackcount = 0
1068                panelcount += 1
1069                # save the start row to plot this panel for future revisit.
1070                if self._panelling != 'r' and \
1071                       len(self._panelrows) < self._ipanel+1+panelcount:
1072                    self._panelrows += [r]
1073                   
1074            #if (b > b0 or newpanel) and stackcount < nstack:
1075            if stackcount < nstack and (newpanel or \
1076                                            rowstack or (a == a0 and b > b0)):
1077                y = []
1078                if len(polmodes):
1079                    y = scan._getspectrum(r, polmodes[scan.getpol(r)])
1080                else:
1081                    y = scan._getspectrum(r)
1082                # flag application
1083                mr = scan._getflagrow(r)
1084                from numpy import ma, array
1085                if mr:
1086                    y = ma.masked_array(y,mask=mr)
1087                else:
1088                    m = scan._getmask(r)
1089                    from numpy import logical_not, logical_and
1090                    if self._maskselection and len(self._usermask) == len(m):
1091                        if d[self._stacking](r) in self._maskselection[self._stacking]:
1092                            m = logical_and(m, self._usermask)
1093                    y = ma.masked_array(y,mask=logical_not(array(m,copy=False)))
1094
1095                x = array(scan._getabcissa(r))
1096                if self._offset:
1097                    x += self._offset
1098                if self._minmaxx is not None:
1099                    s,e = self._slice_indeces(x)
1100                    x = x[s:e]
1101                    y = y[s:e]
1102                if len(x) > 1024 and rcParams['plotter.decimate']:
1103                    fac = len(x)/1024
1104                    x = x[::fac]
1105                    y = y[::fac]
1106                llbl = self._get_label(scan, r, self._stacking, self._lmap)
1107                if isinstance(llbl, list) or isinstance(llbl, tuple):
1108                    if 0 <= stackcount < len(llbl):
1109                        # use user label
1110                        llbl = llbl[stackcount]
1111                    else:
1112                        # get default label
1113                        llbl = self._get_label(scan, r, self._stacking, None)
1114                self._plotter.set_line(label=llbl)
1115                plotit = self._plotter.plot
1116                if self._hist: plotit = self._plotter.hist
1117                if len(x) > 0 and not mr:
1118                    plotit(x,y)
1119                    xlim= self._minmaxx or [min(x),max(x)]
1120                    allxlim += xlim
1121                    ylim= self._minmaxy or [ma.minimum(y),ma.maximum(y)]
1122                    allylim += ylim
1123                else:
1124                    xlim = self._minmaxx or []
1125                    allxlim += xlim
1126                    ylim= self._minmaxy or []
1127                    allylim += ylim
1128                stackcount += 1
1129                a0=a
1130                b0=b
1131                # last in colour stack -> autoscale x
1132                if stackcount == nstack and len(allxlim) > 0:
1133                    allxlim.sort()
1134                    self._plotter.subplots[panelcount-1]['axes'].set_xlim([allxlim[0],allxlim[-1]])
1135                    if ganged:
1136                        allxlim = [allxlim[0],allxlim[-1]]
1137                    else:
1138                        # clear
1139                        allxlim =[]
1140
1141            newpanel = False
1142            #a0=a
1143            #b0=b
1144            # ignore following rows
1145            if (panelcount == n and stackcount == nstack) or (r == nr-1):
1146                # last panel -> autoscale y if ganged
1147                #if rcParams['plotter.ganged'] and len(allylim) > 0:
1148                if ganged and len(allylim) > 0:
1149                    allylim.sort()
1150                    self._plotter.set_limits(ylim=[allylim[0],allylim[-1]])
1151                break
1152            r+=1 # next row
1153
1154        # save the current counter for multi-page plotting
1155        self._startrow = r+1
1156        self._ipanel += panelcount
1157        if self.casabar_exists():
1158            if self._ipanel >= nptot-1:
1159                self._plotter.figmgr.casabar.disable_next()
1160            else:
1161                self._plotter.figmgr.casabar.enable_next()
1162            if self._ipanel + 1 - panelcount > 0:
1163                self._plotter.figmgr.casabar.enable_prev()
1164            else:
1165                self._plotter.figmgr.casabar.disable_prev()
1166
1167        #reset the selector to the scantable's original
1168        scan.set_selection(savesel)
1169
1170        #temporary switch-off for older matplotlib
1171        #if self._fp is not None:
1172        if self._fp is not None and getattr(self._plotter.figure,'findobj',False):
1173            for o in self._plotter.figure.findobj(Text):
1174                o.set_fontproperties(self._fp)
1175
1176    def _get_sortstring(self, lorders):
1177        d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
1178              'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME', 'r':None, '_r':None }
1179        if not (type(lorders) == list) and not (type(lorders) == tuple):
1180            return None
1181        if len(lorders) > 0:
1182            lsorts = []
1183            for order in lorders:
1184                if order == "r":
1185                    # don't sort if row panelling/stacking
1186                    return None
1187                ssort = d0[order]
1188                if ssort:
1189                    lsorts.append(ssort)
1190            return lsorts
1191        return None
1192
1193    def set_selection(self, selection=None, refresh=True, **kw):
1194        """
1195        Parameters:
1196            selection:  a selector object (default unset the selection)
1197            refresh:    True (default) or False. If True, the plot is
1198                        replotted based on the new parameter setting(s).
1199                        Otherwise,the parameter(s) are set without replotting.
1200        """
1201        if selection is None:
1202            # reset
1203            if len(kw) == 0:
1204                self._selection = selector()
1205            else:
1206                # try keywords
1207                for k in kw:
1208                    if k not in selector.fields:
1209                        raise KeyError("Invalid selection key '%s', valid keys are %s" % (k, selector.fields))
1210                self._selection = selector(**kw)
1211        elif isinstance(selection, selector):
1212            self._selection = selection
1213        else:
1214            raise TypeError("'selection' is not of type selector")
1215
1216        order = self._get_sortstring([self._panelling,self._stacking])
1217        if order:
1218            self._selection.set_order(order)
1219        if refresh and self._data:
1220            self.plot()
1221
1222    def _get_selected_n(self, scan):
1223        d1 = {'b': scan.getbeamnos, 's': scan.getscannos,
1224             'i': scan.getifnos, 'p': scan.getpolnos, 't': scan.ncycle,
1225             'r': scan.nrow}#, '_r': False}
1226        d2 = { 'b': self._selection.get_beams(),
1227               's': self._selection.get_scans(),
1228               'i': self._selection.get_ifs(),
1229               'p': self._selection.get_pols(),
1230               't': self._selection.get_cycles(),
1231               'r': False}#, '_r': 1}
1232        n =  d2[self._panelling] or d1[self._panelling]()
1233        nstack = d2[self._stacking] or d1[self._stacking]()
1234        # handle row panelling/stacking
1235        if self._panelling == 'r':
1236            nstack = 1
1237        elif self._stacking == 'r':
1238            n = 1
1239        return n,nstack
1240
1241    def _get_label(self, scan, row, mode, userlabel=None):
1242        if isinstance(userlabel, list) and len(userlabel) == 0:
1243            userlabel = " "
1244        pms = dict(zip(self._selection.get_pols(),self._selection.get_poltypes()))
1245        if len(pms):
1246            poleval = scan._getpollabel(scan.getpol(row),pms[scan.getpol(row)])
1247        else:
1248            poleval = scan._getpollabel(scan.getpol(row),scan.poltype())
1249        d = {'b': "Beam "+str(scan.getbeam(row)),
1250             #'s': scan._getsourcename(row),
1251             's': "Scan "+str(scan.getscan(row))+\
1252                  " ("+str(scan._getsourcename(row))+")",
1253             'i': "IF"+str(scan.getif(row)),
1254             'p': poleval,
1255             't': str(scan.get_time(row)),
1256             'r': "row "+str(row),
1257             #'_r': str(scan.get_time(row))+",\nIF"+str(scan.getif(row))+", "+poleval+", Beam"+str(scan.getbeam(row)) }
1258             '_r': "" }
1259        return userlabel or d[mode]
1260
1261    def plotazel(self, scan=None, outfile=None):
1262        """
1263        plot azimuth and elevation versus time of a scantable
1264        """
1265        visible = rcParams['plotter.gui']
1266        from matplotlib import pylab as PL
1267        from matplotlib.dates import DateFormatter
1268        from pytz import timezone
1269        from matplotlib.dates import HourLocator, MinuteLocator,SecondLocator, DayLocator
1270        from matplotlib.ticker import MultipleLocator
1271        from numpy import array, pi
1272        if not visible or not self._visible:
1273            PL.ioff()
1274            from matplotlib.backends.backend_agg import FigureCanvasAgg
1275            PL.gcf().canvas.switch_backends(FigureCanvasAgg)
1276        self._data = scan
1277        dates = self._data.get_time(asdatetime=True)
1278        t = PL.date2num(dates)
1279        tz = timezone('UTC')
1280        PL.cla()
1281        PL.ioff()
1282        PL.clf()
1283        # Adjust subplot margins
1284        if not self._margins or len(self._margins) != 6:
1285            self.set_margin(refresh=False)
1286        lef, bot, rig, top, wsp, hsp = self._margins
1287        PL.gcf().subplots_adjust(left=lef,bottom=bot,right=rig,top=top,
1288                                 wspace=wsp,hspace=hsp)
1289
1290        tdel = max(t) - min(t)
1291        ax = PL.subplot(2,1,1)
1292        el = array(self._data.get_elevation())*180./pi
1293        PL.ylabel('El [deg.]')
1294        dstr = dates[0].strftime('%Y/%m/%d')
1295        if tdel > 1.0:
1296            dstr2 = dates[len(dates)-1].strftime('%Y/%m/%d')
1297            dstr = dstr + " - " + dstr2
1298            majloc = DayLocator()
1299            minloc = HourLocator(range(0,23,12))
1300            timefmt = DateFormatter("%b%d")
1301        elif tdel > 24./60.:
1302            timefmt = DateFormatter('%H:%M')
1303            majloc = HourLocator()
1304            minloc = MinuteLocator(30)
1305        else:
1306            timefmt = DateFormatter('%H:%M')
1307            majloc = MinuteLocator(interval=5)
1308            minloc = SecondLocator(30)
1309
1310        PL.title(dstr)
1311        if tdel == 0.0:
1312            th = (t - PL.floor(t))*24.0
1313            PL.plot(th,el,'o',markersize=2, markerfacecolor='b', markeredgecolor='b')
1314        else:
1315            PL.plot_date(t,el,'o', markersize=2, markerfacecolor='b', markeredgecolor='b',tz=tz)
1316            #ax.grid(True)
1317            ax.xaxis.set_major_formatter(timefmt)
1318            ax.xaxis.set_major_locator(majloc)
1319            ax.xaxis.set_minor_locator(minloc)
1320        ax.yaxis.grid(True)
1321        yloc = MultipleLocator(30)
1322        ax.set_ylim(0,90)
1323        ax.yaxis.set_major_locator(yloc)
1324        if tdel > 1.0:
1325            labels = ax.get_xticklabels()
1326        #    PL.setp(labels, fontsize=10, rotation=45)
1327            PL.setp(labels, fontsize=10)
1328
1329        # Az plot
1330        az = array(self._data.get_azimuth())*180./pi
1331        if min(az) < 0:
1332            for irow in range(len(az)):
1333                if az[irow] < 0: az[irow] += 360.0
1334
1335        ax2 = PL.subplot(2,1,2)
1336        #PL.xlabel('Time (UT [hour])')
1337        PL.ylabel('Az [deg.]')
1338        if tdel == 0.0:
1339            PL.plot(th,az,'o',markersize=2, markeredgecolor='b',markerfacecolor='b')
1340        else:
1341            PL.plot_date(t,az,'o', markersize=2,markeredgecolor='b',markerfacecolor='b',tz=tz)
1342            ax2.xaxis.set_major_formatter(timefmt)
1343            ax2.xaxis.set_major_locator(majloc)
1344            ax2.xaxis.set_minor_locator(minloc)
1345        #ax2.grid(True)
1346        ax2.set_ylim(0,360)
1347        ax2.yaxis.grid(True)
1348        #hfmt = DateFormatter('%H')
1349        #hloc = HourLocator()
1350        yloc = MultipleLocator(60)
1351        ax2.yaxis.set_major_locator(yloc)
1352        if tdel > 1.0:
1353            labels = ax2.get_xticklabels()
1354            PL.setp(labels, fontsize=10)
1355            PL.xlabel('Time (UT [day])')
1356        else:
1357            PL.xlabel('Time (UT [hour])')
1358
1359        PL.ion()
1360        PL.draw()
1361        if matplotlib.get_backend() == 'Qt4Agg': PL.gcf().show()
1362        if (outfile is not None):
1363           PL.savefig(outfile)
1364
1365    def plotpointing(self, scan=None, outfile=None):
1366        """
1367        plot telescope pointings
1368        """
1369        visible = rcParams['plotter.gui']
1370        from matplotlib import pylab as PL
1371        from numpy import array, pi
1372        if not visible or not self._visible:
1373            PL.ioff()
1374            from matplotlib.backends.backend_agg import FigureCanvasAgg
1375            PL.gcf().canvas.switch_backends(FigureCanvasAgg)
1376        self._data = scan
1377        dir = array(self._data.get_directionval()).transpose()
1378        ra = dir[0]*180./pi
1379        dec = dir[1]*180./pi
1380        PL.cla()
1381        #PL.ioff()
1382        PL.clf()
1383        # Adjust subplot margins
1384        if not self._margins or len(self._margins) != 6:
1385            self.set_margin(refresh=False)
1386        lef, bot, rig, top, wsp, hsp = self._margins
1387        PL.gcf().subplots_adjust(left=lef,bottom=bot,right=rig,top=top,
1388                                 wspace=wsp,hspace=hsp)
1389        ax = PL.gca()
1390        #ax = PL.axes([0.1,0.1,0.8,0.8])
1391        #ax = PL.axes([0.1,0.1,0.8,0.8])
1392        ax.set_aspect('equal')
1393        PL.plot(ra, dec, 'b,')
1394        PL.xlabel('RA [deg.]')
1395        PL.ylabel('Declination [deg.]')
1396        PL.title('Telescope pointings')
1397        [xmin,xmax,ymin,ymax] = PL.axis()
1398        PL.axis([xmax,xmin,ymin,ymax])
1399        PL.ion()
1400        PL.draw()
1401        if matplotlib.get_backend() == 'Qt4Agg': PL.gcf().show()
1402        if (outfile is not None):
1403           PL.savefig(outfile)
1404
1405    # plot total power data
1406    # plotting in time is not yet implemented..
1407    @asaplog_post_dec
1408    def plottp(self, scan=None):
1409        self._assert_plotter(action="reload")
1410        self._plotter.hold()
1411        self._plotter.clear()
1412        from asap import scantable
1413        if not self._data and not scan:
1414            msg = "Input is not a scantable"
1415            raise TypeError(msg)
1416        if isinstance(scan, scantable):
1417            if self._data is not None:
1418                if scan != self._data:
1419                    self._data = scan
1420                    # reset
1421                    self._reset()
1422            else:
1423                self._data = scan
1424                self._reset()
1425        # ranges become invalid when abcissa changes?
1426        #if self._abcunit and self._abcunit != self._data.get_unit():
1427        #    self._minmaxx = None
1428        #    self._minmaxy = None
1429        #    self._abcunit = self._data.get_unit()
1430        #    self._datamask = None
1431
1432        # Adjust subplot margins
1433        if not self._margins or len(self._margins) !=6:
1434            self.set_margin(refresh=False)
1435        lef, bot, rig, top, wsp, hsp = self._margins
1436        self._plotter.figure.subplots_adjust(
1437            left=lef,bottom=bot,right=rig,top=top,wspace=wsp,hspace=hsp)
1438        if self.casabar_exists(): self._plotter.figmgr.casabar.disable_button()
1439        self._plottp(self._data)
1440        if self._minmaxy is not None:
1441            self._plotter.set_limits(ylim=self._minmaxy)
1442        self._plotter.release()
1443        self._plotter.tidy()
1444        self._plotter.show(hardrefresh=False)
1445        return
1446
1447    def _plottp(self,scan):
1448        """
1449        private method for plotting total power data
1450        """
1451        from numpy import ma, array, arange, logical_not
1452        r=0
1453        nr = scan.nrow()
1454        a0,b0 = -1,-1
1455        allxlim = []
1456        allylim = []
1457        y=[]
1458        self._plotter.set_panels()
1459        self._plotter.palette(0)
1460        #title
1461        #xlab = self._abcissa and self._abcissa[panelcount] \
1462        #       or scan._getabcissalabel()
1463        #ylab = self._ordinate and self._ordinate[panelcount] \
1464        #       or scan._get_ordinate_label()
1465        xlab = self._abcissa or 'row number' #or Time
1466        ylab = self._ordinate or scan._get_ordinate_label()
1467        self._plotter.set_axes('xlabel',xlab)
1468        self._plotter.set_axes('ylabel',ylab)
1469        lbl = self._get_label(scan, r, 's', self._title)
1470        if isinstance(lbl, list) or isinstance(lbl, tuple):
1471        #    if 0 <= panelcount < len(lbl):
1472        #        lbl = lbl[panelcount]
1473        #    else:
1474                # get default label
1475             lbl = self._get_label(scan, r, self._panelling, None)
1476        self._plotter.set_axes('title',lbl)
1477        y=array(scan._get_column(scan._getspectrum,-1))
1478        m = array(scan._get_column(scan._getmask,-1))
1479        y = ma.masked_array(y,mask=logical_not(array(m,copy=False)))
1480        x = arange(len(y))
1481        # try to handle spectral data somewhat...
1482        l,m = y.shape
1483        if m > 1:
1484            y=y.mean(axis=1)
1485        plotit = self._plotter.plot
1486        llbl = self._get_label(scan, r, self._stacking, None)
1487        self._plotter.set_line(label=llbl)
1488        if len(x) > 0:
1489            plotit(x,y)
1490
1491
1492    # forwards to matplotlib.Figure.text
1493    def figtext(self, *args, **kwargs):
1494        """
1495        Add text to figure at location x,y (relative 0-1 coords).
1496        This method forwards *args and **kwargs to a Matplotlib method,
1497        matplotlib.Figure.text.
1498        See the method help for detailed information.
1499        """
1500        self._assert_plotter(action="reload")
1501        self._plotter.text(*args, **kwargs)
1502    # end matplotlib.Figure.text forwarding function
1503
1504
1505    # printing header information
1506    @asaplog_post_dec
1507    def print_header(self, plot=True, fontsize=9, logger=False, selstr='', extrastr=''):
1508        """
1509        print data (scantable) header on the plot and/or logger.
1510        To plot the header on the plot, this method should be called after
1511        plotting spectra by the method, asapplotter.plot.
1512        Parameters:
1513            plot:      whether or not print header info on the plot.
1514            fontsize:  header font size (valid only plot=True)
1515            logger:    whether or not print header info on the logger.
1516            selstr:    additional selection string (not verified)
1517            extrastr:  additional string to print at the beginning (not verified)
1518        """
1519        if not plot and not logger:
1520            return
1521        if not self._data:
1522            raise RuntimeError("No scantable has been set yet.")
1523        # Now header will be printed on plot and/or logger.
1524        # Get header information and format it.
1525        ssum=self._data._list_header()
1526        # Print Observation header to the upper-left corner of plot
1527        headstr=[ssum[0:ssum.find('Obs. Type:')]]
1528        headstr.append(ssum[ssum.find('Obs. Type:'):ssum.find('Flux Unit:')])
1529        if extrastr != '':
1530            headstr[0]=extrastr+'\n'+headstr[0]
1531            self._headtext['extrastr'] = extrastr
1532        if selstr != '':
1533            selstr += '\n'
1534            self._headtext['selstr'] = selstr
1535        ssel=(selstr+self._data.get_selection().__str__()+self._selection.__str__() or 'none')
1536        headstr.append('***Selections***\n'+ssel)
1537
1538        if plot:
1539            errmsg = "Can plot header only after the first call to plot()."
1540            self._assert_plotter(action="halt",errmsg=errmsg)
1541            self._plotter.hold()
1542            self._header_plot(headstr,fontsize=fontsize)
1543            import time
1544            self._plotter.figure.text(0.99,0.01,
1545                            time.strftime("%a %d %b %Y  %H:%M:%S %Z"),
1546                            horizontalalignment='right',
1547                            verticalalignment='bottom',fontsize=8)
1548            self._plotter.release()
1549        if logger:
1550            selstr = "Selections:    "+ssel
1551            asaplog.push("----------------\n  Plot Summary\n----------------")
1552            asaplog.push(extrastr)
1553            asaplog.push(ssum[0:ssum.find('Selection:')]\
1554                         + selstr)
1555        self._headtext['string'] = headstr
1556        del ssel, ssum, headstr
1557
1558    def _header_plot(self, texts, fontsize=9):
1559        self._headtext['textobj']=[]
1560        nstcol=len(texts)
1561        for i in range(nstcol):
1562            self._headtext['textobj'].append(
1563                self._plotter.figure.text(0.03+float(i)/nstcol,0.98,
1564                                          texts[i],
1565                                          horizontalalignment='left',
1566                                          verticalalignment='top',
1567                                          fontsize=fontsize))
1568
1569    def clear_header(self):
1570        if not self._headtext['textobj']:
1571            asaplog.push("No header has been plotted. Exit without any operation")
1572            asaplog.post("WARN")
1573        elif self._assert_plotter(action="status"):
1574            self._plotter.hold()
1575            for textobj in self._headtext['textobj']:
1576                #if textobj.get_text() in self._headstring:
1577                try:
1578                    textobj.remove()
1579                except NotImplementedError:
1580                    self._plotter.figure.texts.pop(self._plotter.figure.texts.index(textobj))
1581            self._plotter.release()
1582        self._reset_header()
1583
1584    # plot spectra by pointing
1585    @asaplog_post_dec
1586    def plotgrid(self, scan=None,center=None,spacing=None,rows=None,cols=None):
1587        """
1588        Plot spectra based on direction.
1589       
1590        Parameters:
1591            scan:      a scantable to plot
1592            center:    the grid center direction (a list) of plots in the
1593                       unit of DIRECTION column.
1594                       (default) the center of map region
1595            spacing:   a list of horizontal (R.A.) and vertical (Dec.)
1596                       spacing in the unit of DIRECTION column.
1597                       (default) Calculated by the extent of map region and
1598                       the number of rows and cols to cover
1599            rows:      number of panels (grid points) in horizontal direction
1600            cols:      number of panels (grid points) in vertical direction
1601
1602        Note:
1603        - Only the first IFNO, POLNO, and BEAM in the scantable will be
1604        plotted.
1605        - This method doesn't re-grid and average spectra in scantable. Use
1606        asapgrid module to re-grid spectra before plotting with this method.
1607        Only the first spectrum is plotted in case there are multiple
1608        spectra which belong to a grid.
1609        """
1610        from asap import scantable
1611        from numpy import array, ma, cos
1612        if not self._data and not scan:
1613            msg = "No scantable is specified to plot"
1614            raise TypeError(msg)
1615        if scan:
1616            self.set_data(scan, refresh=False)
1617            del scan
1618
1619        # Rows and cols
1620        if rows:
1621            self._rows = int(rows)
1622        else:
1623            msg = "Number of rows to plot are not specified. "
1624            if self._rows:
1625                msg += "Using previous value = %d" % (self._rows)
1626                asaplog.push(msg)
1627            else:
1628                self._rows = 1
1629                msg += "Setting rows = %d" % (self._rows)
1630                asaplog.post()
1631                asaplog.push(msg)
1632                asaplog.post("WARN")
1633        if cols:
1634            self._cols = int(cols)
1635        else:
1636            msg = "Number of cols to plot are not specified. "
1637            if self._cols:
1638                msg += "Using previous value = %d" % (self._cols)
1639                asaplog.push(msg)
1640            else:
1641                self._cols = 1
1642                msg += "Setting cols = %d" % (self._cols)
1643                asaplog.post()
1644                asaplog.push(msg)
1645                asaplog.post("WARN")
1646
1647        # Center and spacing
1648        if center is None:
1649            #asaplog.post()
1650            asaplog.push("Grid center is not specified. Automatically calculated from pointing center.")
1651            #asaplog.post("WARN")
1652            dirarr = array(self._data.get_directionval()).transpose()
1653            #center = [dirarr[0].mean(), dirarr[1].mean()]
1654            center = [0.5*(dirarr[0].max() + dirarr[0].min()),
1655                      0.5*(dirarr[1].max() + dirarr[1].min())]
1656            del dirarr
1657        elif (type(center) in (list, tuple)) and len(center) > 1:
1658            center = center[0:2]
1659        else:
1660            msg = "Direction of grid center should be a list of float (R.A., Dec.)"
1661            raise ValueError, msg
1662        asaplog.push("Grid center: (%f, %f) " % (center[0],center[1]))
1663
1664        if spacing is None:
1665            #asaplog.post()
1666            asaplog.push("Grid spacing not specified. Automatically calculated from map coverage")
1667            #asaplog.post("WARN")
1668            # automatically get spacing
1669            dirarr = array(self._data.get_directionval()).transpose()
1670            wx = 2. * max(abs(dirarr[0].max()-center[0]),
1671                          abs(dirarr[0].min()-center[0]))
1672            wy = 2. * max(abs(dirarr[1].max()-center[1]),
1673                          abs(dirarr[1].min()-center[1]))
1674            ## slightly expand area to plot the edges
1675            #wx *= 1.01
1676            #wy *= 1.01
1677            #xgrid = wx/self._cols
1678            #ygrid = wy/self._rows
1679            xgrid = wx/max(self._cols-1.,1.)
1680            ygrid = wy/max(self._rows-1.,1.)
1681            #print "Pointing range: (x, y) = (%f - %f, %f - %f)" %\
1682            #  (dirarr[0].min(),dirarr[0].max(),dirarr[1].min(),dirarr[1].max())
1683            # identical R.A. and/or Dec. for all spectra.
1684            if xgrid == 0:
1685                xgrid = 1.
1686            if ygrid == 0:
1687                ygrid = 1.
1688            # spacing should be negative to transpose plot
1689            spacing = [- xgrid, - ygrid]
1690            del dirarr, xgrid, ygrid
1691        #elif isinstance(spacing, str):
1692        #    # spacing is a quantity
1693        elif (type(spacing) in (list, tuple)) and len(spacing) > 1:
1694            for i in xrange(2):
1695                val = spacing[i]
1696                if not isinstance(val, float):
1697                    raise TypeError("spacing should be a list of float")
1698                if val > 0.:
1699                    spacing[i] = -val
1700            spacing = spacing[0:2]
1701            # Correction of Dec. effect
1702            spacing[0] /= cos(center[1])
1703        else:
1704            msg = "Invalid spacing."
1705            raise TypeError(msg)
1706        asaplog.push("Spacing: (%f, %f) (projected)" % (spacing[0],spacing[1]))
1707
1708        ntotpl = self._rows * self._cols
1709        minpos = [center[0]-spacing[0]*self._cols/2.,
1710                  center[1]-spacing[1]*self._rows/2.]
1711        #print "Plot range: (x, y) = (%f - %f, %f - %f)" %\
1712        #      (minpos[0],minpos[0]+spacing[0]*self._cols,
1713        #       minpos[1],minpos[1]+spacing[1]*self._rows)
1714        ifs = self._data.getifnos()
1715        if len(ifs) > 1:
1716            msg = "Found multiple IFs in scantable. Only the first IF (IFNO=%d) will be plotted." % ifs[0]
1717            asaplog.post()
1718            asaplog.push(msg)
1719            asaplog.post("WARN")
1720        pols = self._data.getpolnos()
1721        if len(pols) > 1:
1722            msg = "Found multiple POLs in scantable. Only the first POL (POLNO=%d) will be plotted." % pols[0]
1723            asaplog.post()
1724            asaplog.push(msg)
1725            asaplog.post("WARN")
1726        beams = self._data.getbeamnos()
1727        if len(beams) > 1:
1728            msg = "Found multiple BEAMs in scantable. Only the first BEAM (BEAMNO=%d) will be plotted." % beams[0]
1729            asaplog.post()
1730            asaplog.push(msg)
1731            asaplog.post("WARN")
1732        self._data.set_selection(ifs=[ifs[0]],pols=[pols[0]],beams=[beams[0]])
1733        if self._data.nrow() > ntotpl:
1734            msg = "Scantable is finely sampled than plotting grids. "\
1735                  + "Only the first spectrum is plotted in each grid."
1736            asaplog.post()
1737            asaplog.push(msg)
1738            asaplog.post("WARN")
1739       
1740        self._assert_plotter(action="reload")
1741        self._plotter.hold()
1742        self._plotter.clear()
1743        self._plotter.legend()
1744       
1745        # Adjust subplot margins
1746        if not self._margins or len(self._margins) !=6:
1747            self.set_margin(refresh=False)
1748        self._plotter.set_panels(rows=self._rows,cols=self._cols,
1749                                 nplots=ntotpl,margin=self._margins,ganged=True)
1750        if self.casabar_exists():
1751            self._plotter.figmgr.casabar.set_pagecounter(1)
1752            self._plotter.figmgr.casabar.enable_button()
1753        # Actual plot
1754        npl = 0
1755        for irow in range(self._data.nrow()):
1756            pos = self._data.get_directionval(irow)
1757            ix = int((pos[0] - minpos[0])/spacing[0])
1758            if ix < 0 or ix >= self._cols:
1759                #print "Row %d : Out of X-range (x = %f) ... skipped" % (irow, pos[0])
1760                continue
1761            iy = int((pos[1]- minpos[1])/spacing[1])
1762            if iy < 0 or iy >= self._cols:
1763                #print "Row %d : Out of Y-range (y = %f) ... skipped" % (irow,pos[1])
1764                continue
1765            ipanel = ix + iy*self._cols
1766            if len(self._plotter.subplots[ipanel]['lines']) > 0:
1767                #print "Row %d : panel %d lready plotted ... skipped" % (irow,ipanel)
1768                # a spectrum already plotted in the panel
1769                continue
1770            # Plotting this row
1771            #print "PLOTTING row %d (panel=%d)" % (irow, ipanel)
1772            npl += 1
1773            self._plotter.subplot(ipanel)
1774            self._plotter.palette(0,colormap=self._colormap, \
1775                                  linestyle=0,linestyles=self._linestyles)
1776            xlab = self._abcissa and self._abcissa[ipanel] \
1777                   or self._data._getabcissalabel(irow)
1778            if self._offset and not self._abcissa:
1779                xlab += " (relative)"
1780            ylab = self._ordinate and self._ordinate[ipanel] \
1781                   or self._data._get_ordinate_label()
1782            self._plotter.set_axes('xlabel', xlab)
1783            self._plotter.set_axes('ylabel', ylab)
1784            #from numpy import pi
1785            #lbl = "(%f, %f)" % (self._data.get_directionval(irow)[0]*180/pi,self._data.get_directionval(irow)[1]*180./pi)
1786            lbl = self._data.get_direction(irow)
1787            self._plotter.set_axes('title',lbl)
1788
1789            y = self._data._getspectrum(irow)
1790            # flag application
1791            mr = self._data._getflagrow(irow)
1792            if mr:  # FLAGROW=True
1793                y = ma.masked_array(y,mask=mr)
1794            else:
1795                m = self._data._getmask(irow)
1796                from numpy import logical_not, logical_and
1797                ### user mask is not available so far
1798                #if self._maskselection and len(self._usermask) == len(m):
1799                #    if d[self._stacking](irow) in self._maskselection[self._stacking]:
1800                #            m = logical_and(m, self._usermask)
1801                y = ma.masked_array(y,mask=logical_not(array(m,copy=False)))
1802
1803            x = array(self._data._getabcissa(irow))
1804            if self._offset:
1805                x += self._offset
1806            if self._minmaxx is not None:
1807                s,e = self._slice_indeces(x)
1808                x = x[s:e]
1809                y = y[s:e]
1810            if len(x) > 1024 and rcParams['plotter.decimate']:
1811                fac = len(x)/1024
1812                x = x[::fac]
1813                y = y[::fac]
1814            self._plotter.set_line(label=lbl)
1815            plotit = self._plotter.plot
1816            if self._hist: plotit = self._plotter.hist
1817            if len(x) > 0 and not mr:
1818                plotit(x,y)
1819#                 xlim= self._minmaxx or [min(x),max(x)]
1820#                 allxlim += xlim
1821#                 ylim= self._minmaxy or [ma.minimum(y),ma.maximum(y)]
1822#                 allylim += ylim
1823#             else:
1824#                 xlim = self._minmaxx or []
1825#                 allxlim += xlim
1826#                 ylim= self._minmaxy or []
1827#                 allylim += ylim
1828           
1829            if npl >= ntotpl:
1830                break
1831           
1832        if self._minmaxy is not None:
1833            self._plotter.set_limits(ylim=self._minmaxy)
1834        self._plotter.release()
1835        self._plotter.tidy()
1836        self._plotter.show(hardrefresh=False)
1837        return
Note: See TracBrowser for help on using the repository browser.