source: trunk/python/customgui_base.py @ 2324

Last change on this file since 2324 was 2324, checked in by Malte Marquarding, 13 years ago

Fixed bug in plotetr based statistics where median always showed nan. Need to get stats function explictly from the numpy.ma module. Also put in a filter for the stats to not show stats for annotations. Everything with a label starting with '_' gets ignored.

File size: 46.4 KB
Line 
1import os
2import matplotlib, numpy
3from asap.logging import asaplog, asaplog_post_dec
4from matplotlib.patches import Rectangle
5from asap.parameters import rcParams
6from asap import scantable
7from asap._asap import stmath
8from asap.utils import _n_bools, mask_not, mask_or
9
10######################################
11##    Add CASA custom toolbar       ##
12######################################
13class CustomToolbarCommon:
14    def __init__(self,parent):
15        self.plotter = parent
16        #self.figmgr=self.plotter._plotter.figmgr
17
18    ### select the nearest spectrum in pick radius
19    ###    and display spectral value on the toolbar.
20    def _select_spectrum(self,event):
21        # Do not fire event when in zooming/panning mode
22        mode = self.figmgr.toolbar.mode
23        if not mode == '':
24            return
25            # When selected point is out of panels
26        if event.inaxes == None:
27            return
28        # If not left button
29        if event.button != 1:
30            return
31
32        xclick = event.xdata
33        yclick = event.ydata
34        dist2 = 1000.
35        pickline = None
36        # If the pannel has picable objects
37        pflag = False
38        for lin in event.inaxes.lines:
39            if not lin.pickable():
40                continue
41            pflag = True
42            flag,pind = lin.contains(event)
43            if not flag:
44                continue
45            # Get nearest point
46            inds = pind['ind']
47            xlin = lin.get_xdata()
48            ylin = lin.get_ydata()
49            for i in inds:
50                d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
51                if dist2 >= d2:
52                    dist2 = d2
53                    pickline = lin
54        # No pickcable line in the pannel
55        if not pflag:
56            return
57        # Pickable but too far from mouse position
58        elif pickline is None:
59            picked = 'No line selected.'
60            self.figmgr.toolbar.set_message(picked)
61            return
62        del pind, inds, xlin, ylin
63        # Spectra are Picked
64        theplot = self.plotter._plotter
65        thetoolbar = self.figmgr.toolbar
66        thecanvas = self.figmgr.canvas
67        # Disconnect the default motion notify event
68        # Notice! the other buttons are also diabled!!!
69        thecanvas.mpl_disconnect(thetoolbar._idDrag)
70        # Get picked spectrum
71        xdata = pickline.get_xdata()
72        ydata = pickline.get_ydata()
73        titl = pickline.get_label()
74        titp = event.inaxes.title.get_text()
75        panel0 = event.inaxes
76        picked = "Selected: '"+titl+"' in panel '"+titp+"'."
77        thetoolbar.set_message(picked)
78        # Generate a navigation window
79        #naviwin=Navigationwindow(titp,titl)
80        #------------------------------------------------------#
81        # Show spectrum data at mouse position
82        def spec_data(event):
83            # Getting spectrum data of neiboring point
84            xclick = event.xdata
85            if event.inaxes != panel0:
86                return
87            ipoint = len(xdata)-1
88            for i in range(len(xdata)-1):
89                xl = xclick - xdata[i]
90                xr = xclick - xdata[i+1]
91                if xl*xr <= 0.:
92                    ipoint = i
93                    break
94            # Output spectral value on the navigation window
95            posi = '[ %s, %s ]:  x = %.2f   value = %.2f'\
96                   %(titl,titp,xdata[ipoint],ydata[ipoint])
97            #naviwin.posi.set(posi)
98            thetoolbar.set_message(posi)
99        #------------------------------------------------------#
100        # Disconnect from mouse events
101        def discon(event):
102            #naviwin.window.destroy()
103            theplot.register('motion_notify',None)
104            # Re-activate the default motion_notify_event
105            thetoolbar._idDrag = thecanvas.mpl_connect('motion_notify_event',
106                                                       thetoolbar.mouse_move)
107            theplot.register('button_release',None)
108            return
109        #------------------------------------------------------#
110        # Show data value along with mouse movement
111        theplot.register('motion_notify',spec_data)
112        # Finish events when mouse button is released
113        theplot.register('button_release',discon)
114
115
116    ### Notation
117    def _mod_note(self,event):
118        # Do not fire event when in zooming/panning mode
119        if not self.figmgr.toolbar.mode == '':
120            return
121        if event.button == 1:
122            self.notewin.load_textwindow(event)
123        elif event.button == 3 and self._note_picked(event):
124            self.notewin.load_modmenu(event)
125        return
126
127    def _note_picked(self,event):
128        # just briefly check if any texts are picked
129        for textobj in self.canvas.figure.texts:
130            if textobj.contains(event)[0]:
131                return True
132        for ax in self.canvas.figure.axes:
133            for textobj in ax.texts:
134                if textobj.contains(event)[0]:
135                    return True
136        #print "No text picked"
137        return False
138
139
140    ### Purely plotter based statistics calculation of a selected area.
141    ### No access to scantable
142    def _single_mask(self,event):
143        # Do not fire event when in zooming/panning mode
144        if not self.figmgr.toolbar.mode == '':
145            return
146        # When selected point is out of panels
147        if event.inaxes == None:
148            return
149        if event.button == 1:
150            exclude=False
151        elif event.button == 3:
152            exclude=True
153        else:
154            return
155
156        self._thisregion = {'axes': event.inaxes,'xs': event.x,
157                            'worldx': [event.xdata,event.xdata],
158                            'invert': exclude}
159        self.xold = event.x
160        self.xdataold = event.xdata
161
162        self.plotter._plotter.register('button_press',None)
163        self.plotter._plotter.register('motion_notify', self._xspan_draw)
164        self.plotter._plotter.register('button_press', self._xspan_end)
165
166    def _xspan_draw(self,event):
167        if event.inaxes == self._thisregion['axes']:
168            xnow = event.x
169            self.xold = xnow
170            self.xdataold = event.xdata
171        else:
172            xnow = self.xold
173        try: self.lastspan
174        except AttributeError: pass
175        else:
176            if self.lastspan: self._remove_span(self.lastspan)
177
178        self.lastspan = self._draw_span(self._thisregion['axes'],
179                                        self._thisregion['xs'], xnow, fill="")
180        del xnow
181
182    def _draw_span(self,axes,x0,x1,**kwargs):
183        pass
184
185    def _remove_span(self,span):
186        pass
187
188    @asaplog_post_dec
189    def _xspan_end(self,event):
190        if not self.figmgr.toolbar.mode == '':
191            return
192        #if event.button != 1:
193        #    return
194
195        try: self.lastspan
196        except AttributeError: pass
197        else:
198            self._remove_span(self.lastspan)
199            del self.lastspan
200        if event.inaxes == self._thisregion['axes']:
201            xdataend = event.xdata
202        else:
203            xdataend = self.xdataold
204
205        self._thisregion['worldx'][1] = xdataend
206        # print statistics of spectra in subplot
207        self._subplot_stats(self._thisregion)
208       
209        # release event
210        self.plotter._plotter.register('button_press',None)
211        self.plotter._plotter.register('motion_notify',None)
212        # Clear up region selection
213        self._thisregion = None
214        self.xdataold = None
215        self.xold = None
216        # finally recover region selection event
217        self.plotter._plotter.register('button_press',self._single_mask)
218
219    def _subplot_stats(self,selection):
220        statstr = ['max', 'min', 'median', 'mean', 'sum', 'std'] #'rms']
221        panelstr = selection['axes'].title.get_text()
222        ssep = "-"*70
223        asaplog.push(ssep)
224        asaplog.post()
225        for line in selection['axes'].lines:
226            # Don't include annotations
227            if line.get_label().startswith("_"):
228                continue
229            label = panelstr + ", "+line.get_label()
230            x = line.get_xdata()
231            newmsk = None
232            selmsk = self._create_flag_from_array(x,
233                                                  selection['worldx'],
234                                                  selection['invert'])
235            ydat = None
236            y = line.get_ydata()
237            if numpy.ma.isMaskedArray(y):
238                ydat = y.data
239                basemsk = y.mask
240            else:
241                ydat = y
242                basemsk = False
243            if not isinstance(basemsk, bool):
244                # should be ndarray
245                newmsk = mask_or(selmsk, basemsk)
246            elif basemsk:
247                # the whole original spectrum is flagged
248                newmsk = basemsk
249            else:
250                # no channel was flagged originally
251                newmsk = selmsk
252            mdata = numpy.ma.masked_array(ydat, mask=newmsk)
253            statval = {}
254            for stat in statstr:
255                # need to get the stat functions from the ma module!!!
256                statval[stat] = getattr(numpy.ma,stat)(mdata)
257            self._print_stats(statval, statstr=statstr, label=label,\
258                              mask=selection['worldx'],\
259                              unmask=selection['invert'])
260            asaplog.push(ssep)
261            asaplog.post()
262
263    def _create_flag_from_array(self,x,masklist,invert):
264        # Return True for channels which should be EXCLUDED (flag)
265        if len(masklist) <= 1:
266            asaplog.push()
267            asaplog.post("masklist should be a list of 2 elements")
268            asaplog.push("ERROR")
269        n = len(x)
270        # Base mask: flag out all channels
271        mask = _n_bools(n, True)
272        minval = min(masklist[0:2])
273        maxval = max(masklist[0:2])
274        for i in range(n):
275            if minval <= x[i] <= maxval:
276                mask[i] = False
277        if invert:
278            mask = mask_not(mask)
279        return mask
280
281    @asaplog_post_dec
282    def _print_stats(self,stats,statstr=None,label="",mask=None,unmask=False):
283        if not isinstance(stats,dict) or len(stats) == 0:
284            asaplog.post()
285            asaplog.push("Invalid statistic value")
286            asaplog.post("ERROR")
287        maskstr = "Not available"
288        if mask:
289            masktype = "mask"
290            maskstr = str(mask)
291            if unmask: masktype = "unmask"
292
293        sout = label + ", " + masktype + " = " + maskstr + "\n"
294        statvals = []
295        if not len(statstr):
296            statstr = stats.keys()
297        for key in statstr:
298            sout += key.ljust(10)
299            statvals.append(stats.pop(key))
300        sout += "\n"
301        sout += ("%f "*len(statstr) % tuple(statvals))
302        asaplog.push(sout)
303        #del sout, maskstr, masktype, statvals, key, stats, statstr, mask, label
304
305
306    ### Page chages
307    ### go to the previous page
308    def prev_page(self):
309        self.figmgr.toolbar.set_message('plotting the previous page')
310        #self._pause_buttons(operation="start",msg='plotting the previous page')
311        self._new_page(goback=True)
312        #self._pause_buttons(operation="end")
313
314    ### go to the next page
315    def next_page(self):
316        self.figmgr.toolbar.set_message('plotting the next page')
317        #self._pause_buttons(operation="start",msg='plotting the next page')
318        self._new_page(goback=False)
319        #self._pause_buttons(operation="end")
320
321    ### actual plotting of the new page
322    def _new_page(self,goback=False):
323        top = None
324        header = self.plotter._headtext
325        reset = False
326        doheader = (isinstance(header['textobj'],list) and \
327                    len(header['textobj']) > 0)
328        if self.plotter._startrow <= 0:
329            msg = "The page counter is reset due to chages of plot settings. "
330            msg += "Plotting from the first page."
331            asaplog.push(msg)
332            asaplog.post('WARN')
333            reset = True
334            goback = False
335            if doheader:
336                extrastr = selstr = ''
337                if header.has_key('extrastr'):
338                    extrastr = header['extrastr']
339                if header.has_key('selstr'):
340                    selstr = header['selstr']
341            self.plotter._reset_header()
342        if doheader:
343            top = self.plotter._plotter.figure.subplotpars.top
344            fontsize = header['textobj'][0].get_fontproperties().get_size()
345
346        self.plotter._plotter.hold()
347        if goback:
348            self._set_prevpage_counter()
349        #self.plotter._plotter.clear()
350        self.plotter._plot(self.plotter._data)
351        self.set_pagecounter(self._get_pagenum())
352        # Plot header information
353        if header['textobj']:
354            if top and top != self.plotter._margins[3]:
355                # work around for sdplot in CASA. complete checking in future?
356                self.plotter._plotter.figure.subplots_adjust(top=top)
357            if reset:
358                self.plotter.print_header(plot=True,fontsize=fontsize,selstr=selstr, extrastr=extrastr)
359            else:
360                self.plotter._header_plot(header['string'],fontsize=fontsize)
361        self.plotter._plotter.release()
362        self.plotter._plotter.tidy()
363        self.plotter._plotter.show(hardrefresh=False)
364        del top
365
366    ### calculate the panel ID and start row to plot the previous page
367    def _set_prevpage_counter(self):
368        # set row and panel counters to those of the 1st panel of previous page
369        maxpanel = 16
370        # the ID of the last panel in current plot
371        lastpanel = self.plotter._ipanel
372        # the number of current subplots
373        currpnum = len(self.plotter._plotter.subplots)
374        # the nuber of previous subplots
375        prevpnum = None
376        if self.plotter._rows and self.plotter._cols:
377            # when user set layout
378            prevpnum = self.plotter._rows*self.plotter._cols
379        else:
380            # no user specification
381            prevpnum = maxpanel
382
383        start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
384        # set the pannel ID of the last panel of prev-prev page
385        self.plotter._ipanel = start_ipanel-1
386        if self.plotter._panelling == 'r':
387            self.plotter._startrow = start_ipanel
388        else:
389            # the start row number of the next panel
390            self.plotter._startrow = self.plotter._panelrows[start_ipanel]
391        del lastpanel,currpnum,prevpnum,start_ipanel
392
393    ### refresh the page counter
394    def set_pagecounter(self,page):
395        nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
396        nwidth = max(nwidth,4)
397        formatstr = '%'+str(nwidth)+'d'
398        self.show_pagenum(page,formatstr)
399
400    def show_pagenum(self,pagenum,formatstr):
401        # passed to backend dependent class
402        pass       
403
404    def _get_pagenum(self):
405        maxpanel = 16
406        # get the ID of last panel in the current page
407        idlastpanel = self.plotter._ipanel
408        if self.plotter._rows and self.plotter._cols:
409            ppp = self.plotter._rows*self.plotter._cols
410        else:
411            ppp = maxpanel
412        return int(idlastpanel/ppp)+1
413
414    # pause buttons for slow operations. implemented at a backend dependent class
415    def _pause_buttons(self,operation="end",msg=""):
416        pass
417
418
419
420
421######################################
422##    Notation box window           ##
423######################################
424class NotationWindowCommon:
425    """
426    A base class to define the functions that the backend-based
427    GUI notation class must implement to print/modify/delete notes on a canvas.
428
429    The following methods *must* be implemented in the backend-based
430    parent class:
431        _get_note : get text in text box
432        _get_anchval : get anchor value selected
433    """
434    def __init__(self,master=None):
435        #self.parent = master
436        self.canvas = master
437        self.event = None
438        self.note = None
439        self.anchors = ["figure","axes","data"]
440        self.seltext = {}
441        self.numnote = 0
442
443    @asaplog_post_dec
444    def print_text(self):
445        """
446        Print a note on a canvas specified with the Notation window.
447        Called when 'print' button selected on the window.
448        """
449        anchor = self.anchors[self._get_anchval()]
450        notestr = self._get_note().rstrip("\n")
451        if len(notestr.strip()) == 0:
452            #self._clear_textbox()
453            #print "Empty string!"
454            return
455
456        myaxes = None
457        calcpos = True
458        xpos = None
459        ypos = None
460        if self.seltext:
461            # You are modifying a text
462            mycanvas = self.canvas
463            oldanch = self.seltext['anchor']
464            if oldanch != 'figure':
465                myaxes = self.seltext['parent']
466            calcpos = (anchor != oldanch)
467            if not calcpos:
468                # printing text in the same coord.
469                # you don't have to recalc position
470                parent = self.seltext['parent']
471                transform = self.seltext['textobj'].get_transform()
472                (xpos, ypos) = self.seltext['textobj'].get_position()
473            elif anchor == "figure":
474                # converting from "axes"/"data" -> "figure"
475                (x, y) = self.seltext['textobj']._get_xy_display()
476            elif oldanch == "data":
477                # converting from "data" -> "axes".
478                # need xdata & ydata in the axes
479                (x, y) = self.seltext['textobj'].get_position()
480            else:
481                # converting "figure"/"axes" -> "data"
482                # need to calculate xdata & ydata in the axes
483                pixpos = self.seltext['textobj']._get_xy_display()
484                (w,h) = mycanvas.get_width_height()
485                relpos = (pixpos[0]/float(w), pixpos[1]/float(h))
486                if not myaxes:
487                    myaxes = self._get_axes_from_pos(relpos,mycanvas)
488                    if not myaxes:
489                        raise RuntimeError, "Axes resolution failed!"
490                (x, y) = self._convert_pix2dat(relpos,myaxes)
491            self._remove_seltext()
492        elif self.event:
493            mycanvas = self.event.canvas
494            myaxes = self.event.inaxes
495            if myaxes and (anchor != "figure"):
496                x = self.event.xdata
497                y = self.event.ydata
498            else:
499                x = self.event.x
500                y = self.event.y
501        else:
502            raise RuntimeError, "No valid position to print data"
503            return
504
505        # now you know
506        picker = True
507        # alignment of the text: ha (horizontal), va (vertical)
508        ha = 'left'
509        #va = 'center'
510        va = 'top'
511        if not calcpos:
512            # you aready know parent, tansform, xpos and ypos
513            pass
514        elif anchor == "figure":
515            # text instance will be appended to mycanvas.figure.texts
516            parent = mycanvas.figure
517            transform = parent.transFigure
518            (w,h) = mycanvas.get_width_height()
519            xpos = x/float(w)
520            ypos = y/float(h)           
521        elif myaxes:
522            ## text instance will be appended to myaxes.texts
523            parent = myaxes
524            if anchor == "axes":
525                transform = myaxes.transAxes
526                lims = myaxes.get_xlim()
527                xpos = (x-lims[0])/(lims[1]-lims[0])
528                lims = myaxes.get_ylim()
529                ypos = (y-lims[0])/(lims[1]-lims[0])
530            else:
531                # anchored on "data"
532                transform = myaxes.transData
533                xpos = x
534                ypos = y
535        parent.text(xpos,ypos,notestr,transform=transform,
536                    ha=ha,va=va,picker=picker)
537        mycanvas.draw()
538
539        self.numnote += 1
540
541        #self._clear_textbox()
542        msg = "Added note: '"+notestr+"'"
543        msg += " @["+str(xpos)+", "+str(ypos)+"] ("+anchor+"-coord)"
544        msg += "\ntotal number of notes are "+str(self.numnote)
545        asaplog.push( msg )
546
547    def _get_axes_from_pos(self,pos,canvas):
548        """helper function to get axes of a position in a plot (fig-coord)"""
549        if len(pos) != 2:
550            raise ValueError, "pixel position should have 2 elements"
551        for axes in canvas.figure.axes:
552            ##check if pos is in the axes
553            #if axes.contains_point(pos): ### seems not working
554            #    return axes
555            try:
556                axbox = axes.get_position().get_points()
557            except AttributeError: ### WORKAROUND for old matplotlib
558                axbox = self._oldpos2new(axes.get_position())
559            if (axbox[0][0] <= pos[0] <= axbox[1][0]) and \
560               (axbox[0][1] <= pos[1] <= axbox[1][1]):
561                return axes
562        return None
563
564    ### WORKAROUND for old matplotlib
565    def _oldpos2new(self,oldpos=None):
566        return [[oldpos[0],oldpos[1]],[oldpos[0]+oldpos[2],oldpos[1]+oldpos[3]]]
567       
568    def _convert_pix2dat(self,pos,axes):
569        """
570        helper function to convert a position in figure-coord (0-1) to
571        data-coordinate of the axes       
572        """
573        # convert a relative position from lower-left of the canvas
574        # to a data in axes
575        if len(pos) != 2:
576            raise ValueError, "pixel position should have 2 elements"
577        # left-/bottom-pixel, and pixel width & height of the axes
578        bbox = axes.get_position()
579        try:
580            axpos = bbox.get_points()
581        except AttributeError: ### WORKAROUND for old matplotlib
582            axpos = self._oldpos2new(bbox)
583        # check pos value
584        if (pos[0] < axpos[0][0]) or (pos[1] < axpos[0][1]) \
585               or (pos[0] > axpos[1][0]) or (pos[1] > axpos[1][1]):
586            raise ValueError, "The position is out of the axes"
587        xlims = axes.get_xlim()
588        ylims = axes.get_ylim()
589        wdat = xlims[1] - xlims[0]
590        hdat = ylims[1] - ylims[0]
591        xdat = xlims[0] + wdat*(pos[0] - axpos[0][0])/(axpos[1][0] - axpos[0][0])
592        ydat = ylims[0] + hdat*(pos[1] - axpos[0][1])/(axpos[1][1] - axpos[0][1])
593        return (xdat, ydat)
594
595    @asaplog_post_dec
596    def _get_selected_text(self,event):
597        """helper function to return a dictionary of the nearest note to the event."""
598        (w,h) = event.canvas.get_width_height()
599        dist2 = w*w+h*h
600        selected = {}
601        for textobj in self.canvas.figure.texts:
602            if textobj.contains(event)[0]:
603                d2 = self._get_text_dist2(event,textobj)
604                if dist2 >= d2:
605                    dist2 = d2
606                    selected = {'anchor': 'figure', \
607                                'parent': event.canvas.figure, 'textobj': textobj}
608                    msg = "Fig loop: a text, '"+textobj.get_text()+"', at "
609                    msg += str(textobj.get_position())+" detected"
610                    #print msg
611        for ax in self.canvas.figure.axes:
612            for textobj in ax.texts:
613                if textobj.contains(event)[0]:
614                    d2 = self._get_text_dist2(event,textobj)
615                    if dist2 >= d2:
616                        anchor='axes'
617                        if ax.transData == textobj.get_transform():
618                            anchor = 'data'                   
619                        selected = {'anchor': anchor, \
620                                    'parent': ax, 'textobj': textobj}
621                        msg = "Ax loop: a text, '"+textobj.get_text()+"', at "
622                        msg += str(textobj.get_position())+" detected"
623                        #print msg
624
625        if selected:
626            msg = "Selected (modify/delete): '"+selected['textobj'].get_text()
627            msg += "' @"+str(selected['textobj'].get_position())
628            msg += " ("+selected['anchor']+"-coord)"
629            asaplog.push(msg)
630
631        return selected
632
633    def _get_text_dist2(self,event,textobj):
634        """
635        helper function to calculate square of distance between
636        a event position and a text object.
637        """
638        (x,y) = textobj._get_xy_display()
639        return (x-event.x)**2+(y-event.y)**2
640
641    def delete_note(self):
642        """
643        Remove selected note.
644        """
645        #print "You selected 'OK'"
646        self._remove_seltext()
647        self.canvas.draw()
648
649    @asaplog_post_dec
650    def _remove_seltext(self):
651        """helper function to remove the selected note"""
652        if len(self.seltext) < 3:
653            raise ValueError, "Don't under stand selected text obj."
654            return
655        try:
656            self.seltext['textobj'].remove()
657        except NotImplementedError:
658                self.seltext['parent'].texts.pop(self.seltext['parent'].texts.index(self.seltext['textobj']))
659        self.numnote -= 1
660
661        textobj = self.seltext['textobj']
662        msg = "Deleted note: '"+textobj.get_text()+"'"
663        msg += "@"+str(textobj.get_position())\
664               +" ("+self.seltext['anchor']+"-coord)"
665        msg += "\ntotal number of notes are "+str(self.numnote)
666        asaplog.push( msg )
667
668        self.seltext = {}
669
670    @asaplog_post_dec
671    def cancel_delete(self):
672        """
673        Cancel deleting the selected note.
674        Called when 'cancel' button selected on confirmation dialog.
675        """
676        asaplog.push( "Cancel deleting: '"+self.seltext['textobj'].get_text()+"'" )
677        self.seltext = {}
678
679
680
681###########################################
682##    Add CASA custom Flag toolbar       ##
683###########################################
684class CustomFlagToolbarCommon:
685    def __init__(self,parent):
686        self.plotter=parent
687        #self.figmgr=self.plotter._plotter.figmgr
688        self._selregions = {}
689        self._selpanels = []
690        self._polygons = []
691        self._thisregion = None
692        self.xdataold=None
693
694    ### select the nearest spectrum in pick radius
695    ###    and display spectral value on the toolbar.
696    def _select_spectrum(self,event):
697        # Do not fire event when in zooming/panning mode
698        mode = self.figmgr.toolbar.mode
699        if not mode == '':
700            return
701            # When selected point is out of panels
702        if event.inaxes == None:
703            return
704        # If not left button
705        if event.button != 1:
706            return
707
708        xclick = event.xdata
709        yclick = event.ydata
710        dist2 = 1000.
711        pickline = None
712        # If the pannel has picable objects
713        pflag = False
714        for lin in event.inaxes.lines:
715            if not lin.pickable():
716                continue
717            pflag = True
718            flag,pind = lin.contains(event)
719            if not flag:
720                continue
721            # Get nearest point
722            inds = pind['ind']
723            xlin = lin.get_xdata()
724            ylin = lin.get_ydata()
725            for i in inds:
726                d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
727                if dist2 >= d2:
728                    dist2 = d2
729                    pickline = lin
730        # No pickcable line in the pannel
731        if not pflag:
732            return
733        # Pickable but too far from mouse position
734        elif pickline is None:
735            picked = 'No line selected.'
736            self.figmgr.toolbar.set_message(picked)
737            return
738        del pind, inds, xlin, ylin
739        # Spectra are Picked
740        theplot = self.plotter._plotter
741        thetoolbar = self.figmgr.toolbar
742        thecanvas = self.figmgr.canvas
743        # Disconnect the default motion notify event
744        # Notice! the other buttons are also diabled!!!
745        thecanvas.mpl_disconnect(thetoolbar._idDrag)
746        # Get picked spectrum
747        xdata = pickline.get_xdata()
748        ydata = pickline.get_ydata()
749        titl = pickline.get_label()
750        titp = event.inaxes.title.get_text()
751        panel0 = event.inaxes
752        picked = "Selected: '"+titl+"' in panel '"+titp+"'."
753        thetoolbar.set_message(picked)
754        # Generate a navigation window
755        #naviwin=Navigationwindow(titp,titl)
756        #------------------------------------------------------#
757        # Show spectrum data at mouse position
758        def spec_data(event):
759            # Getting spectrum data of neiboring point
760            xclick = event.xdata
761            if event.inaxes != panel0:
762                return
763            ipoint = len(xdata)-1
764            for i in range(len(xdata)-1):
765                xl = xclick-xdata[i]
766                xr = xclick-xdata[i+1]
767                if xl*xr <= 0.:
768                    ipoint = i
769                    break
770            # Output spectral value on the navigation window
771            posi = '[ %s, %s ]:  x = %.2f   value = %.2f'\
772                  %(titl,titp,xdata[ipoint],ydata[ipoint])
773            #naviwin.posi.set(posi)
774            thetoolbar.set_message(posi)
775        #------------------------------------------------------#
776        # Disconnect from mouse events
777        def discon(event):
778            #naviwin.window.destroy()
779            theplot.register('motion_notify',None)
780            # Re-activate the default motion_notify_event
781            thetoolbar._idDrag=thecanvas.mpl_connect('motion_notify_event',
782                                                     thetoolbar.mouse_move)
783            theplot.register('button_release',None)
784            return
785        #------------------------------------------------------#
786        # Show data value along with mouse movement
787        theplot.register('motion_notify',spec_data)
788        # Finish events when mouse button is released
789        theplot.register('button_release',discon)
790
791
792    ### Notation
793    def _mod_note(self,event):
794        # Do not fire event when in zooming/panning mode
795        if not self.figmgr.toolbar.mode == '':
796            return
797        if event.button == 1:
798            self.notewin.load_textwindow(event)
799        elif event.button == 3 and self._note_picked(event):
800            self.notewin.load_modmenu(event)
801        return
802
803    def _note_picked(self,event):
804        # just briefly check if any texts are picked
805        for textobj in self.canvas.figure.texts:
806            if textobj.contains(event)[0]:
807                return True
808        for ax in self.canvas.figure.axes:
809            for textobj in ax.texts:
810                if textobj.contains(event)[0]:
811                    return True
812        return False
813
814    ### Region/Panel selection & oparations
815    ### add regions to selections
816    @asaplog_post_dec
817    def _add_region(self,event):
818        if not self.figmgr.toolbar.mode == '':
819            return
820        if event.button != 1 or event.inaxes == None:
821            return
822        # this row resolution assumes row panelling
823        irow = int(self._getrownum(event.inaxes))
824        if irow in self._selpanels:
825            msg = "The whole spectrum is already selected"
826            asaplog.post()
827            asaplog.push(msg)
828            asaplog.post('WARN')
829            return
830        self._thisregion = {'axes': event.inaxes,'xs': event.x,
831                            'worldx': [event.xdata,event.xdata]}
832        self.plotter._plotter.register('button_press',None)
833        self.xold = event.x
834        self.xdataold = event.xdata
835        self.plotter._plotter.register('motion_notify', self._xspan_draw)
836        self.plotter._plotter.register('button_press', self._xspan_end)
837
838    def _xspan_draw(self,event):
839        if event.inaxes == self._thisregion['axes']:
840            xnow = event.x
841            self.xold = xnow
842            self.xdataold = event.xdata
843        else:
844            xnow = self.xold
845        try: self.lastspan
846        except AttributeError: pass
847        else:
848            if self.lastspan: self._remove_span(self.lastspan)
849
850        #self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="#555555",stipple="gray50")
851        self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="")
852        del xnow
853
854    def _draw_span(self,axes,x0,x1,**kwargs):
855        pass
856
857    def _remove_span(self,span):
858        pass
859
860    @asaplog_post_dec
861    def _xspan_end(self,event):
862        if not self.figmgr.toolbar.mode == '':
863            return
864        if event.button != 1:
865            return
866
867        try: self.lastspan
868        except AttributeError: pass
869        else:
870            self._remove_span(self.lastspan)
871            del self.lastspan
872        if event.inaxes == self._thisregion['axes']:
873            xdataend = event.xdata
874        else:
875            xdataend = self.xdataold
876
877        self._thisregion['worldx'][1] = xdataend
878        lregion = self._thisregion['worldx']
879        pregion = self._thisregion['axes'].axvspan(lregion[0],lregion[1],
880                                                   facecolor='0.7')
881        self.plotter._plotter.canvas.draw()
882        self._polygons.append(pregion)
883        srow = self._getrownum(self._thisregion['axes'])
884        irow = int(srow)
885        if not self._selregions.has_key(srow):
886            self._selregions[srow] = []
887        self._selregions[srow].append(lregion)
888        del lregion, pregion, xdataend
889        sout = "selected region: "+str(self._thisregion['worldx'])+\
890              "(@row "+str(self._getrownum(self._thisregion['axes']))+")"
891        asaplog.push(sout)
892
893        # release event
894        self.plotter._plotter.register('button_press',None)
895        self.plotter._plotter.register('motion_notify',None)
896        # Clear up region selection
897        self._thisregion = None
898        self.xdataold = None
899        self.xold = None
900        # finally recover region selection event
901        self.plotter._plotter.register('button_press',self._add_region)
902
903    ### add panels to selections
904    @asaplog_post_dec
905    def _add_panel(self,event):
906        if not self.figmgr.toolbar.mode == '':
907            return
908        if event.button != 1 or event.inaxes == None:
909            return
910        selax = event.inaxes
911        # this row resolution assumes row panelling
912        srow = self._getrownum(selax)
913        irow = int(srow)
914        if srow:
915            self._selpanels.append(irow)
916        shadow = Rectangle((0,0),1,1,facecolor='0.7',transform=selax.transAxes,visible=True)
917        self._polygons.append(selax.add_patch(shadow))
918        #self.plotter._plotter.show(False)
919        self.plotter._plotter.canvas.draw()
920        asaplog.push("row "+str(irow)+" is selected")
921        ## check for region selection of the spectra and overwrite it.
922        ##!!!! currently disabled for consistency with flag tools !!!!
923        #if self._selregions.has_key(srow):
924        #    self._selregions.pop(srow)
925        #    msg = "The whole spectrum is selected for row="+srow+". Region selection will be overwritten."
926        #    asaplog.push(msg)
927
928    def _getrownum(self,axis):
929        ### returns the row number of selected spectrum as a string ###
930        plabel = axis.get_title()
931        if plabel.startswith("row "):
932            return plabel.strip("row ")
933        return None
934
935    def _any_selection(self):
936        ### returns if users have selected any spectrum or region ###
937        if len(self._selpanels) or len(self._selregions):
938            return True
939        return False
940
941    def _plot_selections(self,regions=None,panels=None):
942        ### mark panels/spectra selections in the page
943        if not self._any_selection() and not (regions or panels):
944            return
945        regions = regions or self._selregions.copy() or {}
946        panels = panels or self._selpanels or []
947        if not isinstance(regions,dict):
948            asaplog.post()
949            asaplog.push("Invalid region specification")
950            asaplog.post('ERROR')
951        if not isinstance(panels,list):
952            asaplog.post()
953            asaplog.push("Invalid panel specification")
954            asaplog.post('ERROR')
955        strow = self._getrownum(self.plotter._plotter.subplots[0]['axes'])
956        enrow = self._getrownum(self.plotter._plotter.subplots[-1]['axes'])
957        for irow in range(int(strow),int(enrow)+1):
958            if regions.has_key(str(irow)):
959                ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
960                mlist = regions.pop(str(irow))
961                for i in range(len(mlist)):
962                    self._polygons.append(ax.axvspan(mlist[i][0],mlist[i][1],
963                                                     facecolor='0.7'))
964                del ax,mlist
965            if irow in panels:
966                ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
967                shadow = Rectangle((0,0),1,1,facecolor='0.7',
968                                   transform=ax.transAxes,visible=True)
969                self._polygons.append(ax.add_patch(shadow))
970                del ax,shadow
971        self.plotter._plotter.canvas.draw()
972        del regions,panels,strow,enrow
973
974    def _clear_selection_plot(self, refresh=True):
975        ### clear up polygons which mark selected spectra and regions ###
976        if len(self._polygons) > 0:
977            for shadow in self._polygons:
978                shadow.remove()
979            if refresh: self.plotter._plotter.canvas.draw()
980        self._polygons = []
981
982    def _clearup_selections(self, refresh=True):
983        # clear-up selection and polygons
984        self._selpanels = []
985        self._selregions = {}
986        self._clear_selection_plot(refresh=refresh)
987
988    ### clear up selections
989    def cancel_select(self):
990        self.figmgr.toolbar.set_message('selections canceled')
991        # clear-up selection and polygons
992        self._clearup_selections(refresh=True)
993
994    ### flag selected spectra/regions
995    @asaplog_post_dec
996    def flag(self):
997        if not self._any_selection():
998            msg = "No selection to be Flagged"
999            asaplog.post()
1000            asaplog.push(msg)
1001            asaplog.post('WARN')
1002            return
1003        self._pause_buttons(operation="start",msg="Flagging data...")
1004        self._flag_operation(rows=self._selpanels,
1005                             regions=self._selregions,unflag=False)
1006        sout = "Flagged:\n"
1007        sout += "  rows = "+str(self._selpanels)+"\n"
1008        sout += "  regions: "+str(self._selregions)
1009        asaplog.push(sout)
1010        del sout
1011        self.plotter._ismodified = True
1012        self._clearup_selections(refresh=False)
1013        self._plot_page(pagemode="current")
1014        self._pause_buttons(operation="end")
1015
1016    ### unflag selected spectra/regions
1017    @asaplog_post_dec
1018    def unflag(self):
1019        if not self._any_selection():
1020            msg = "No selection to be Flagged"
1021            asaplog.push(msg)
1022            asaplog.post('WARN')
1023            return
1024        self._pause_buttons(operation="start",msg="Unflagging data...")
1025        self._flag_operation(rows=self._selpanels,
1026                             regions=self._selregions,unflag=True)
1027        sout = "Unflagged:\n"
1028        sout += "  rows = "+str(self._selpanels)+"\n"
1029        sout += "  regions: "+str(self._selregions)
1030        asaplog.push(sout)
1031        del sout
1032        self.plotter._ismodified = True
1033        self._clearup_selections(refresh=False)
1034        self._plot_page(pagemode="current")
1035        self._pause_buttons(operation="end")
1036
1037    ### actual flag operation
1038    @asaplog_post_dec
1039    def _flag_operation(self,rows=None,regions=None,unflag=False):
1040        scan = self.plotter._data
1041        if not scan:
1042            asaplog.post()
1043            asaplog.push("Invalid scantable")
1044            asaplog.post("ERROR")
1045        if isinstance(rows,list) and len(rows) > 0:
1046            scan.flag_row(rows=rows,unflag=unflag)
1047        if isinstance(regions,dict) and len(regions) > 0:
1048            for srow, masklist in regions.iteritems():
1049                if not isinstance(masklist,list) or len(masklist) ==0:
1050                    msg = "Ignoring invalid region selection for row = "+srow
1051                    asaplog.post()
1052                    asaplog.push(msg)
1053                    asaplog.post("WARN")
1054                    continue
1055                irow = int(srow)
1056                mask = scan.create_mask(masklist,invert=False,row=irow)
1057                scan.flag(row=irow,mask=mask,unflag=unflag)
1058                del irow, mask
1059            del srow, masklist
1060        del scan
1061
1062    ### show statistics of selected spectra/regions
1063    @asaplog_post_dec
1064    def stat_cal(self):
1065        if not self._any_selection():
1066            msg = "No selection to be calculated"
1067            asaplog.push(msg)
1068            asaplog.post('WARN')
1069            return
1070        self._selected_stats(rows=self._selpanels,regions=self._selregions)
1071        self._clearup_selections(refresh=True)
1072
1073    @asaplog_post_dec
1074    def _selected_stats(self,rows=None,regions=None):
1075        scan = self.plotter._data
1076        if not scan:
1077            asaplog.post()
1078            asaplog.push("Invalid scantable")
1079            asaplog.post("ERROR")
1080        mathobj = stmath( rcParams['insitu'] )
1081        statval = {}
1082        statstr = ['max', 'min', 'mean', 'median', 'sum', 'stddev', 'rms']
1083        if isinstance(rows, list) and len(rows) > 0:
1084            for irow in rows:
1085                for stat in statstr:
1086                    statval[stat] = mathobj._statsrow(scan,[],stat,irow)[0]
1087                self._print_stats(scan,irow,statval,statstr=statstr)
1088            del irow
1089        if isinstance(regions,dict) and len(regions) > 0:
1090            for srow, masklist in regions.iteritems():
1091                if not isinstance(masklist,list) or len(masklist) ==0:
1092                    msg = "Ignoring invalid region selection for row = "+srow
1093                    asaplog.post()
1094                    asaplog.push(msg)
1095                    asaplog.post("WARN")
1096                    continue
1097                irow = int(srow)
1098                mask = scan.create_mask(masklist,invert=False,row=irow)
1099                for stat in statstr:
1100                    statval[stat] = mathobj._statsrow(scan,mask,stat,irow)[0]
1101                self._print_stats(scan,irow,statval,statstr=statstr,
1102                                  mask=masklist)
1103                del irow, mask
1104            del srow, masklist
1105        del scan, statval, mathobj
1106
1107    @asaplog_post_dec
1108    def _print_stats(self,scan,row,stats,statstr=None,mask=None):
1109        if not isinstance(scan, scantable):
1110            asaplog.post()
1111            asaplog.push("Invalid scantable")
1112            asaplog.post("ERROR")
1113        if row < 0 or row > scan.nrow():
1114            asaplog.post()
1115            asaplog.push("Invalid row number")
1116            asaplog.post("ERROR")
1117        if not isinstance(stats,dict) or len(stats) == 0:
1118            asaplog.post()
1119            asaplog.push("Invalid statistic value")
1120            asaplog.post("ERROR")
1121        maskstr = "All"
1122        if mask:
1123            maskstr = str(mask)
1124        ssep = "-"*70+"\n"
1125        sout = ssep
1126        sout += ("Row=%d  Scan=%d  IF=%d  Pol=%d  Time=%s  mask=%s" % \
1127                 (row, scan.getscan(row), scan.getif(row), scan.getpol(row), scan.get_time(row),maskstr))
1128        sout += "\n"
1129        statvals = []
1130        if not len(statstr):
1131            statstr = stats.keys()
1132        for key in statstr:
1133            sout += key.ljust(10)
1134            statvals.append(stats.pop(key))
1135        sout += "\n"
1136        sout += ("%f "*len(statstr) % tuple(statvals))
1137        sout += "\n"+ssep
1138        asaplog.push(sout)
1139        del sout, ssep, maskstr, statvals, key, scan, row, stats, statstr, mask
1140
1141    ### Page chages
1142    ### go to the previous page
1143    def prev_page(self):
1144        self._pause_buttons(operation="start",msg='plotting the previous page')
1145        self._clear_selection_plot(refresh=False)
1146        self._plot_page(pagemode="prev")
1147        self._plot_selections()
1148        self._pause_buttons(operation="end")
1149
1150    ### go to the next page
1151    def next_page(self):
1152        self._pause_buttons(operation="start",msg='plotting the next page')
1153        self._clear_selection_plot(refresh=False)
1154        self._plot_page(pagemode="next")
1155        self._plot_selections()
1156        self._pause_buttons(operation="end")
1157
1158    ### actual plotting of the new page
1159    def _plot_page(self,pagemode="next"):
1160        if self.plotter._startrow <= 0:
1161            msg = "The page counter is reset due to chages of plot settings. "
1162            msg += "Plotting from the first page."
1163            asaplog.post()
1164            asaplog.push(msg)
1165            asaplog.post('WARN')
1166            goback = False
1167
1168        self.plotter._plotter.hold()
1169        self.plotter._plotter.legend(1)
1170        self._set_plot_counter(pagemode)
1171        self.plotter._plot(self.plotter._data)
1172        self.set_pagecounter(self._get_pagenum())
1173        self.plotter._plotter.release()
1174        self.plotter._plotter.tidy()
1175        self.plotter._plotter.show(hardrefresh=False)
1176
1177    ### calculate the panel ID and start row to plot a page
1178    #def _set_prevpage_counter(self):
1179    def _set_plot_counter(self, pagemode):
1180        ## page operation should be either "previous", "current", or "next"
1181        availpage = ["p","c","n"]
1182        pageop = pagemode[0].lower()
1183        if not (pageop in availpage):
1184            asaplog.post()
1185            asaplog.push("Invalid page operation")
1186            asaplog.post("ERROR")
1187        if pageop == "n":
1188            # nothing necessary to plot the next page
1189            return
1190        # set row and panel counters to those of the 1st panel of previous page
1191        maxpanel = 16
1192        # the ID of the last panel in current plot
1193        lastpanel = self.plotter._ipanel
1194        # the number of current subplots
1195        currpnum = len(self.plotter._plotter.subplots)
1196
1197        # the nuber of previous subplots
1198        start_ipanel = None
1199        if pageop == "c":
1200            start_ipanel = max(lastpanel-currpnum+1, 0)
1201        else:
1202            ## previous page
1203            prevpnum = None
1204            if self.plotter._rows and self.plotter._cols:
1205                # when user set layout
1206                prevpnum = self.plotter._rows*self.plotter._cols
1207            else:
1208                # no user specification
1209                prevpnum = maxpanel
1210            start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
1211            del prevpnum
1212
1213        # set the pannel ID of the last panel of the prev(-prev) page
1214        self.plotter._ipanel = start_ipanel-1
1215        if self.plotter._panelling == 'r':
1216            self.plotter._startrow = start_ipanel
1217        else:
1218            # the start row number of the next panel
1219            self.plotter._startrow = self.plotter._panelrows[start_ipanel]
1220        del lastpanel,currpnum,start_ipanel
1221
1222    ### refresh the page counter
1223    def set_pagecounter(self,page):
1224        nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
1225        nwidth = max(nwidth,4)
1226        formatstr = '%'+str(nwidth)+'d'
1227        self.show_pagenum(page,formatstr)
1228
1229    def show_pagenum(self,pagenum,formatstr):
1230        # passed to backend dependent class
1231        pass
1232
1233    def _get_pagenum(self):
1234        maxpanel = 16
1235        # get the ID of last panel in the current page
1236        idlastpanel = self.plotter._ipanel
1237        if self.plotter._rows and self.plotter._cols:
1238            ppp = self.plotter._rows*self.plotter._cols
1239        else:
1240            ppp = maxpanel
1241        return int(idlastpanel/ppp)+1
1242
1243    # pause buttons for slow operations. implemented at a backend dependent class
1244    def _pause_buttons(self,operation="end",msg=""):
1245        pass
Note: See TracBrowser for help on using the repository browser.