source: trunk/python/asapplotter.py @ 1148

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

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

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