source: trunk/python/asapplotter.py @ 1116

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

fixed up import of MaskedArray? to make it usable with numpy and numarray

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