source: trunk/python/asapplotter.py @ 2576

Last change on this file since 2576 was 2576, checked in by Kana Sugimoto, 12 years ago

New Development: Yes

JIRA Issue: Yes (CAS-1814/ATNF Trac #271)

Ready for Test: Yes

Interface Changes: Yes

What Interface Changed: added a new method, asapplotter.plotgrid

Test Programs:

Put in Release Notes: No

Module(s): asapplotter, sdplot

Description:

Added a new method, plotgrid, in the asapplotter class.
The methods samples spectra in a scantable by sky position recorded in
the DIRECTION column and plot them. User can specify center and spacing in
sky position to sample spectra. The number of spectra to be sampled in
horizontal (R.A.) and vertical (Dec.) direction should be specified as well.
An example of usage is,

import asap as sd
scan = sd.scantable("SOME_NAME",average=False)
sd.asapplotter.plotgrid(scan,center=(-3.133363,-0.329469),spacing=(-0.000272,-0.000257),rows=8,cols=8)

This example will sample 8x8 spectra and plot them by sky position.
So far, center and spacing should be defined in the unit of DIRECTION column.

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