source: trunk/python/asapplotter.py @ 1146

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

added linecatalog plotting; fixed numarray imports

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 23.6 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(),self._selection.get_poltypes()))
477        n,nstack = self._get_selected_n(scan)
478        maxpanel, maxstack = 16,8
479        if n > maxpanel or nstack > maxstack:
480            from asap import asaplog
481            msg ="Scan to be plotted contains more than %d selections.\n" \
482                  "Selecting first %d selections..." % (maxpanel,maxpanel)
483            asaplog.push(msg)
484            print_log()
485            n = min(n,maxpanel)
486            nstack = min(nstack,maxstack)
487
488        if n > 1:
489            ganged = rcParams['plotter.ganged']
490            if self._rows and self._cols:
491                n = min(n,self._rows*self._cols)
492                self._plotter.set_panels(rows=self._rows,cols=self._cols,
493                                         nplots=n,ganged=ganged)
494            else:
495                self._plotter.set_panels(rows=n,cols=0,nplots=n,ganged=ganged)
496        else:
497            self._plotter.set_panels()
498        r=0
499        nr = scan.nrow()
500        a0,b0 = -1,-1
501        allxlim = []
502        allylim = []
503        newpanel=True
504        panelcount,stackcount = 0,0
505        while r < nr:
506            a = d[self._panelling](r)
507            b = d[self._stacking](r)
508            if a > a0 and panelcount < n:
509                if n > 1:
510                    self._plotter.subplot(panelcount)
511                self._plotter.palette(0)
512                #title
513                xlab = self._abcissa and self._abcissa[panelcount] \
514                       or scan._getabcissalabel()
515                ylab = self._ordinate and self._ordinate[panelcount] \
516                       or scan._get_ordinate_label()
517                self._plotter.set_axes('xlabel',xlab)
518                self._plotter.set_axes('ylabel',ylab)
519                lbl = self._get_label(scan, r, self._panelling, self._title)
520                if isinstance(lbl, list) or isinstance(lbl, tuple):
521                    if 0 <= panelcount < len(lbl):
522                        lbl = lbl[panelcount]
523                    else:
524                        # get default label
525                        lbl = self._get_label(scan, r, self._panelling, None)
526                self._plotter.set_axes('title',lbl)
527                newpanel = True
528                stackcount =0
529                panelcount += 1
530            if (b > b0 or newpanel) and stackcount < nstack:
531                y = []
532                if len(polmodes):
533                    y = scan._getspectrum(r, polmodes[scan.getpol(r)])
534                else:
535                    y = scan._getspectrum(r)
536                m = scan._getmask(r)
537                from matplotlib.numerix import logical_not, logical_and
538                if self._maskselection and len(self._usermask) == len(m):
539                    if d[self._stacking](r) in self._maskselection[self._stacking]:
540                        m = logical_and(m, self._usermask)
541                x = scan._getabcissa(r)
542                from matplotlib.numerix import ma, array
543                y = ma.masked_array(y,mask=logical_not(array(m,copy=False)))
544                if self._minmaxx is not None:
545                    s,e = self._slice_indeces(x)
546                    x = x[s:e]
547                    y = y[s:e]
548                if len(x) > 1024 and rcParams['plotter.decimate']:
549                    fac = len(x)/1024
550                    x = x[::fac]
551                    y = y[::fac]
552                llbl = self._get_label(scan, r, self._stacking, self._lmap)
553                if isinstance(llbl, list) or isinstance(llbl, tuple):
554                    if 0 <= stackcount < len(llbl):
555                        # use user label
556                        llbl = llbl[stackcount]
557                    else:
558                        # get default label
559                        llbl = self._get_label(scan, r, self._stacking, None)
560                self._plotter.set_line(label=llbl)
561                plotit = self._plotter.plot
562                if self._hist: plotit = self._plotter.hist
563                if len(x) > 0:
564                    plotit(x,y)
565                    xlim= self._minmaxx or [min(x),max(x)]
566                    allxlim += xlim
567                    ylim= self._minmaxy or [ma.minimum(y),ma.maximum(y)]
568                    allylim += ylim
569                stackcount += 1
570                # last in colour stack -> autoscale x
571                if stackcount == nstack:
572                    allxlim.sort()
573                    self._plotter.axes.set_xlim([allxlim[0],allxlim[-1]])
574                    # clear
575                    allxlim =[]
576
577            newpanel = False
578            a0=a
579            b0=b
580            # ignore following rows
581            if (panelcount == n) and (stackcount == nstack):
582                # last panel -> autoscale y if ganged
583                if rcParams['plotter.ganged']:
584                    allylim.sort()
585                    self._plotter.set_limits(ylim=[allylim[0],allylim[-1]])
586                break
587            r+=1 # next row
588        #reset the selector to the scantable's original
589        scan.set_selection(savesel)
590
591    def set_selection(self, selection=None, refresh=True):
592        self._selection = isinstance(selection,selector) and selection or selector()
593        d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
594              'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME' }
595        order = [d0[self._panelling],d0[self._stacking]]
596        self._selection.set_order(order)
597        if self._data and refresh: self.plot(self._data)
598
599    def _get_selected_n(self, scan):
600        d1 = {'b': scan.nbeam, 's': scan.nscan,
601             'i': scan.nif, 'p': scan.npol, 't': scan.ncycle }
602        d2 = { 'b': len(self._selection.get_beams()),
603               's': len(self._selection.get_scans()),
604               'i': len(self._selection.get_ifs()),
605               'p': len(self._selection.get_pols()),
606               't': len(self._selection.get_cycles()) }
607        n =  d2[self._panelling] or d1[self._panelling]()
608        nstack = d2[self._stacking] or d1[self._stacking]()
609        return n,nstack
610
611    def _get_label(self, scan, row, mode, userlabel=None):
612        pms = dict(zip(self._selection.get_pols(),self._selection.get_poltypes()))
613        if len(pms):
614            poleval = scan._getpollabel(scan.getpol(row),pms[scan.getpol(row)])
615        else:
616            poleval = scan._getpollabel(scan.getpol(row),scan.poltype())
617        d = {'b': "Beam "+str(scan.getbeam(row)),
618             's': scan._getsourcename(row),
619             'i': "IF"+str(scan.getif(row)),
620             'p': poleval,
621             't': scan._gettime(row) }
622        return userlabel or d[mode]
Note: See TracBrowser for help on using the repository browser.