source: trunk/python/asapplotter.py @ 1021

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

decimate threshold 1024 -> 2048; added histogram plotting; function documentation updates

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