source: trunk/python/asapplotter.py @ 1018

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

added yrange synch in 'ganged' mode.

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