source: trunk/python/customgui_base.py @ 2697

Last change on this file since 2697 was 2697, checked in by Kana Sugimoto, 11 years ago

New Development: No

JIRA Issue: No

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs: iterate pages

Put in Release Notes: No

Module(s): asapplotter and sdplot

Description: An attempt to speed-up page iterations in ASAP plotter.


File size: 46.6 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 doheader:
329            top = self.plotter._plotter.figure.subplotpars.top
330            fontsize = header['textobj'][0].get_fontproperties().get_size()
331        if self.plotter._startrow <= 0:
332            msg = "The page counter is reset due to chages of plot settings. "
333            msg += "Plotting from the first page."
334            asaplog.push(msg)
335            asaplog.post('WARN')
336            reset = True
337            goback = False
338            if doheader:
339                extrastr = selstr = ''
340                if header.has_key('extrastr'):
341                    extrastr = header['extrastr']
342                if header.has_key('selstr'):
343                    selstr = header['selstr']
344            self.plotter._reset_header()
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        pagenum = self._get_pagenum()
352        self.set_pagecounter(pagenum)
353        # Plot header information
354        #if header['textobj']:
355        if doheader and pagenum == 1:
356            if top and top != self.plotter._margins[3]:
357                # work around for sdplot in CASA. complete checking in future?
358                self.plotter._plotter.figure.subplots_adjust(top=top)
359            if reset:
360                self.plotter.print_header(plot=True,fontsize=fontsize,selstr=selstr, extrastr=extrastr)
361            else:
362                self.plotter._header_plot(header['string'],fontsize=fontsize)
363        self.plotter._plotter.release()
364        self.plotter._plotter.tidy()
365        self.plotter._plotter.show(hardrefresh=False)
366        del top
367
368    ### calculate the panel ID and start row to plot the previous page
369    def _set_prevpage_counter(self):
370        # set row and panel counters to those of the 1st panel of previous page
371        maxpanel = 16
372        # the ID of the last panel in current plot
373        lastpanel = self.plotter._ipanel
374        # the number of current subplots
375        currpnum = len(self.plotter._plotter.subplots)
376        # the nuber of previous subplots
377        prevpnum = None
378        if self.plotter._rows and self.plotter._cols:
379            # when user set layout
380            prevpnum = self.plotter._rows*self.plotter._cols
381        else:
382            # no user specification
383            prevpnum = maxpanel
384
385        start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
386        # set the pannel ID of the last panel of prev-prev page
387        self.plotter._ipanel = start_ipanel-1
388        if self.plotter._panelling == 'r':
389            self.plotter._startrow = start_ipanel
390        else:
391            # the start row number of the next panel
392            self.plotter._startrow = self.plotter._panelrows[start_ipanel]
393        del lastpanel,currpnum,prevpnum,start_ipanel
394
395    ### refresh the page counter
396    def set_pagecounter(self,page):
397        nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
398        nwidth = max(nwidth,4)
399        formatstr = '%'+str(nwidth)+'d'
400        self.show_pagenum(page,formatstr)
401
402    def show_pagenum(self,pagenum,formatstr):
403        # passed to backend dependent class
404        pass       
405
406    def _get_pagenum(self):
407        # get the ID of last panel in the current page
408        idlastpanel = self.plotter._ipanel
409        # max panels in a page
410        ppp = self.plotter._plotter.rows*self.plotter._plotter.cols
411        return int(idlastpanel/ppp)+1
412
413    # pause buttons for slow operations. implemented at a backend dependent class
414    def _pause_buttons(self,operation="end",msg=""):
415        pass
416
417
418
419
420######################################
421##    Notation box window           ##
422######################################
423class NotationWindowCommon:
424    """
425    A base class to define the functions that the backend-based
426    GUI notation class must implement to print/modify/delete notes on a canvas.
427
428    The following methods *must* be implemented in the backend-based
429    parent class:
430        _get_note : get text in text box
431        _get_anchval : get anchor value selected
432    """
433    def __init__(self,master=None):
434        #self.parent = master
435        self.canvas = master
436        self.event = None
437        self.note = None
438        self.anchors = ["figure","axes","data"]
439        self.seltext = {}
440        self.numnote = 0
441
442    @asaplog_post_dec
443    def print_text(self):
444        """
445        Print a note on a canvas specified with the Notation window.
446        Called when 'print' button selected on the window.
447        """
448        anchor = self.anchors[self._get_anchval()]
449        notestr = self._get_note().rstrip("\n")
450        if len(notestr.strip()) == 0:
451            #self._clear_textbox()
452            #print "Empty string!"
453            return
454
455        myaxes = None
456        calcpos = True
457        xpos = None
458        ypos = None
459        if self.seltext:
460            # You are modifying a text
461            mycanvas = self.canvas
462            oldanch = self.seltext['anchor']
463            if oldanch != 'figure':
464                myaxes = self.seltext['parent']
465            calcpos = (anchor != oldanch)
466            if not calcpos:
467                # printing text in the same coord.
468                # you don't have to recalc position
469                parent = self.seltext['parent']
470                transform = self.seltext['textobj'].get_transform()
471                (xpos, ypos) = self.seltext['textobj'].get_position()
472            elif anchor == "figure":
473                # converting from "axes"/"data" -> "figure"
474                (x, y) = self.seltext['textobj']._get_xy_display()
475            elif oldanch == "data":
476                # converting from "data" -> "axes".
477                # need xdata & ydata in the axes
478                (x, y) = self.seltext['textobj'].get_position()
479            else:
480                # converting "figure"/"axes" -> "data"
481                # need to calculate xdata & ydata in the axes
482                pixpos = self.seltext['textobj']._get_xy_display()
483                (w,h) = mycanvas.get_width_height()
484                relpos = (pixpos[0]/float(w), pixpos[1]/float(h))
485                if not myaxes:
486                    myaxes = self._get_axes_from_pos(relpos,mycanvas)
487                    if not myaxes:
488                        raise RuntimeError, "Axes resolution failed!"
489                (x, y) = self._convert_pix2dat(relpos,myaxes)
490            self._remove_seltext()
491        elif self.event:
492            mycanvas = self.event.canvas
493            myaxes = self.event.inaxes
494            if myaxes and (anchor != "figure"):
495                x = self.event.xdata
496                y = self.event.ydata
497            else:
498                x = self.event.x
499                y = self.event.y
500        else:
501            raise RuntimeError, "No valid position to print data"
502            return
503
504        # now you know
505        picker = True
506        # alignment of the text: ha (horizontal), va (vertical)
507        ha = 'left'
508        #va = 'center'
509        va = 'top'
510        if not calcpos:
511            # you aready know parent, tansform, xpos and ypos
512            pass
513        elif anchor == "figure":
514            # text instance will be appended to mycanvas.figure.texts
515            parent = mycanvas.figure
516            transform = parent.transFigure
517            (w,h) = mycanvas.get_width_height()
518            xpos = x/float(w)
519            ypos = y/float(h)           
520        elif myaxes:
521            ## text instance will be appended to myaxes.texts
522            parent = myaxes
523            if anchor == "axes":
524                transform = myaxes.transAxes
525                lims = myaxes.get_xlim()
526                xpos = (x-lims[0])/(lims[1]-lims[0])
527                lims = myaxes.get_ylim()
528                ypos = (y-lims[0])/(lims[1]-lims[0])
529            else:
530                # anchored on "data"
531                transform = myaxes.transData
532                xpos = x
533                ypos = y
534        parent.text(xpos,ypos,notestr,transform=transform,
535                    ha=ha,va=va,picker=picker)
536        mycanvas.draw()
537
538        self.numnote += 1
539
540        #self._clear_textbox()
541        msg = "Added note: '"+notestr+"'"
542        msg += " @["+str(xpos)+", "+str(ypos)+"] ("+anchor+"-coord)"
543        msg += "\ntotal number of notes are "+str(self.numnote)
544        asaplog.push( msg )
545
546    def _get_axes_from_pos(self,pos,canvas):
547        """helper function to get axes of a position in a plot (fig-coord)"""
548        if len(pos) != 2:
549            raise ValueError, "pixel position should have 2 elements"
550        for axes in canvas.figure.axes:
551            ##check if pos is in the axes
552            #if axes.contains_point(pos): ### seems not working
553            #    return axes
554            try:
555                axbox = axes.get_position().get_points()
556            except AttributeError: ### WORKAROUND for old matplotlib
557                axbox = self._oldpos2new(axes.get_position())
558            if (axbox[0][0] <= pos[0] <= axbox[1][0]) and \
559               (axbox[0][1] <= pos[1] <= axbox[1][1]):
560                return axes
561        return None
562
563    ### WORKAROUND for old matplotlib
564    def _oldpos2new(self,oldpos=None):
565        return [[oldpos[0],oldpos[1]],[oldpos[0]+oldpos[2],oldpos[1]+oldpos[3]]]
566       
567    def _convert_pix2dat(self,pos,axes):
568        """
569        helper function to convert a position in figure-coord (0-1) to
570        data-coordinate of the axes       
571        """
572        # convert a relative position from lower-left of the canvas
573        # to a data in axes
574        if len(pos) != 2:
575            raise ValueError, "pixel position should have 2 elements"
576        # left-/bottom-pixel, and pixel width & height of the axes
577        bbox = axes.get_position()
578        try:
579            axpos = bbox.get_points()
580        except AttributeError: ### WORKAROUND for old matplotlib
581            axpos = self._oldpos2new(bbox)
582        # check pos value
583        if (pos[0] < axpos[0][0]) or (pos[1] < axpos[0][1]) \
584               or (pos[0] > axpos[1][0]) or (pos[1] > axpos[1][1]):
585            raise ValueError, "The position is out of the axes"
586        xlims = axes.get_xlim()
587        ylims = axes.get_ylim()
588        wdat = xlims[1] - xlims[0]
589        hdat = ylims[1] - ylims[0]
590        xdat = xlims[0] + wdat*(pos[0] - axpos[0][0])/(axpos[1][0] - axpos[0][0])
591        ydat = ylims[0] + hdat*(pos[1] - axpos[0][1])/(axpos[1][1] - axpos[0][1])
592        return (xdat, ydat)
593
594    @asaplog_post_dec
595    def _get_selected_text(self,event):
596        """helper function to return a dictionary of the nearest note to the event."""
597        (w,h) = event.canvas.get_width_height()
598        dist2 = w*w+h*h
599        selected = {}
600        for textobj in self.canvas.figure.texts:
601            if textobj.contains(event)[0]:
602                d2 = self._get_text_dist2(event,textobj)
603                if dist2 >= d2:
604                    dist2 = d2
605                    selected = {'anchor': 'figure', \
606                                'parent': event.canvas.figure, 'textobj': textobj}
607                    msg = "Fig loop: a text, '"+textobj.get_text()+"', at "
608                    msg += str(textobj.get_position())+" detected"
609                    #print msg
610        for ax in self.canvas.figure.axes:
611            for textobj in ax.texts:
612                if textobj.contains(event)[0]:
613                    d2 = self._get_text_dist2(event,textobj)
614                    if dist2 >= d2:
615                        anchor='axes'
616                        if ax.transData == textobj.get_transform():
617                            anchor = 'data'                   
618                        selected = {'anchor': anchor, \
619                                    'parent': ax, 'textobj': textobj}
620                        msg = "Ax loop: a text, '"+textobj.get_text()+"', at "
621                        msg += str(textobj.get_position())+" detected"
622                        #print msg
623
624        if selected:
625            msg = "Selected (modify/delete): '"+selected['textobj'].get_text()
626            msg += "' @"+str(selected['textobj'].get_position())
627            msg += " ("+selected['anchor']+"-coord)"
628            asaplog.push(msg)
629
630        return selected
631
632    def _get_text_dist2(self,event,textobj):
633        """
634        helper function to calculate square of distance between
635        a event position and a text object.
636        """
637        (x,y) = textobj._get_xy_display()
638        return (x-event.x)**2+(y-event.y)**2
639
640    def delete_note(self):
641        """
642        Remove selected note.
643        """
644        #print "You selected 'OK'"
645        self._remove_seltext()
646        self.canvas.draw()
647
648    @asaplog_post_dec
649    def _remove_seltext(self):
650        """helper function to remove the selected note"""
651        if len(self.seltext) < 3:
652            raise ValueError, "Don't under stand selected text obj."
653            return
654        try:
655            self.seltext['textobj'].remove()
656        except NotImplementedError:
657                self.seltext['parent'].texts.pop(self.seltext['parent'].texts.index(self.seltext['textobj']))
658        self.numnote -= 1
659
660        textobj = self.seltext['textobj']
661        msg = "Deleted note: '"+textobj.get_text()+"'"
662        msg += "@"+str(textobj.get_position())\
663               +" ("+self.seltext['anchor']+"-coord)"
664        msg += "\ntotal number of notes are "+str(self.numnote)
665        asaplog.push( msg )
666
667        self.seltext = {}
668
669    @asaplog_post_dec
670    def cancel_delete(self):
671        """
672        Cancel deleting the selected note.
673        Called when 'cancel' button selected on confirmation dialog.
674        """
675        asaplog.push( "Cancel deleting: '"+self.seltext['textobj'].get_text()+"'" )
676        self.seltext = {}
677
678
679
680###########################################
681##    Add CASA custom Flag toolbar       ##
682###########################################
683class CustomFlagToolbarCommon:
684    def __init__(self,parent):
685        self.plotter=parent
686        #self.figmgr=self.plotter._plotter.figmgr
687        self._selregions = {}
688        self._selpanels = []
689        self._polygons = []
690        self._thisregion = None
691        self.xdataold=None
692
693    ### select the nearest spectrum in pick radius
694    ###    and display spectral value on the toolbar.
695    def _select_spectrum(self,event):
696        # Do not fire event when in zooming/panning mode
697        mode = self.figmgr.toolbar.mode
698        if not mode == '':
699            return
700            # When selected point is out of panels
701        if event.inaxes == None:
702            return
703        # If not left button
704        if event.button != 1:
705            return
706
707        xclick = event.xdata
708        yclick = event.ydata
709        dist2 = 1000.
710        pickline = None
711        # If the pannel has picable objects
712        pflag = False
713        for lin in event.inaxes.lines:
714            if not lin.pickable():
715                continue
716            pflag = True
717            flag,pind = lin.contains(event)
718            if not flag:
719                continue
720            # Get nearest point
721            inds = pind['ind']
722            xlin = lin.get_xdata()
723            ylin = lin.get_ydata()
724            for i in inds:
725                d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
726                if dist2 >= d2:
727                    dist2 = d2
728                    pickline = lin
729        # No pickcable line in the pannel
730        if not pflag:
731            return
732        # Pickable but too far from mouse position
733        elif pickline is None:
734            picked = 'No line selected.'
735            self.figmgr.toolbar.set_message(picked)
736            return
737        del pind, inds, xlin, ylin
738        # Spectra are Picked
739        theplot = self.plotter._plotter
740        thetoolbar = self.figmgr.toolbar
741        thecanvas = self.figmgr.canvas
742        # Disconnect the default motion notify event
743        # Notice! the other buttons are also diabled!!!
744        thecanvas.mpl_disconnect(thetoolbar._idDrag)
745        # Get picked spectrum
746        xdata = pickline.get_xdata()
747        ydata = pickline.get_ydata()
748        titl = pickline.get_label()
749        titp = event.inaxes.title.get_text()
750        panel0 = event.inaxes
751        picked = "Selected: '"+titl+"' in panel '"+titp+"'."
752        thetoolbar.set_message(picked)
753        # Generate a navigation window
754        #naviwin=Navigationwindow(titp,titl)
755        #------------------------------------------------------#
756        # Show spectrum data at mouse position
757        def spec_data(event):
758            # Getting spectrum data of neiboring point
759            xclick = event.xdata
760            if event.inaxes != panel0:
761                return
762            ipoint = len(xdata)-1
763            for i in range(len(xdata)-1):
764                xl = xclick-xdata[i]
765                xr = xclick-xdata[i+1]
766                if xl*xr <= 0.:
767                    ipoint = i
768                    break
769            # Output spectral value on the navigation window
770            posi = '[ %s, %s ]:  x = %.2f   value = %.2f'\
771                  %(titl,titp,xdata[ipoint],ydata[ipoint])
772            #naviwin.posi.set(posi)
773            thetoolbar.set_message(posi)
774        #------------------------------------------------------#
775        # Disconnect from mouse events
776        def discon(event):
777            #naviwin.window.destroy()
778            theplot.register('motion_notify',None)
779            # Re-activate the default motion_notify_event
780            thetoolbar._idDrag=thecanvas.mpl_connect('motion_notify_event',
781                                                     thetoolbar.mouse_move)
782            theplot.register('button_release',None)
783            return
784        #------------------------------------------------------#
785        # Show data value along with mouse movement
786        theplot.register('motion_notify',spec_data)
787        # Finish events when mouse button is released
788        theplot.register('button_release',discon)
789
790
791    ### Notation
792    def _mod_note(self,event):
793        # Do not fire event when in zooming/panning mode
794        if not self.figmgr.toolbar.mode == '':
795            return
796        if event.button == 1:
797            self.notewin.load_textwindow(event)
798        elif event.button == 3 and self._note_picked(event):
799            self.notewin.load_modmenu(event)
800        return
801
802    def _note_picked(self,event):
803        # just briefly check if any texts are picked
804        for textobj in self.canvas.figure.texts:
805            if textobj.contains(event)[0]:
806                return True
807        for ax in self.canvas.figure.axes:
808            for textobj in ax.texts:
809                if textobj.contains(event)[0]:
810                    return True
811        return False
812
813    ### Region/Panel selection & oparations
814    ### add regions to selections
815    @asaplog_post_dec
816    def _add_region(self,event):
817        if not self.figmgr.toolbar.mode == '':
818            return
819        if event.button != 1 or event.inaxes == None:
820            return
821        # this row resolution assumes row panelling
822        irow = int(self._getrownum(event.inaxes))
823        if irow in self._selpanels:
824            msg = "The whole spectrum is already selected"
825            asaplog.post()
826            asaplog.push(msg)
827            asaplog.post('WARN')
828            return
829        self._thisregion = {'axes': event.inaxes,'xs': event.x,
830                            'worldx': [event.xdata,event.xdata]}
831        self.plotter._plotter.register('button_press',None)
832        self.xold = event.x
833        self.xdataold = event.xdata
834        self.plotter._plotter.register('motion_notify', self._xspan_draw)
835        self.plotter._plotter.register('button_press', self._xspan_end)
836
837    def _xspan_draw(self,event):
838        if event.inaxes == self._thisregion['axes']:
839            xnow = event.x
840            self.xold = xnow
841            self.xdataold = event.xdata
842        else:
843            xnow = self.xold
844        try: self.lastspan
845        except AttributeError: pass
846        else:
847            if self.lastspan: self._remove_span(self.lastspan)
848
849        #self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="#555555",stipple="gray50")
850        self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="")
851        del xnow
852
853    def _draw_span(self,axes,x0,x1,**kwargs):
854        pass
855
856    def _remove_span(self,span):
857        pass
858
859    @asaplog_post_dec
860    def _xspan_end(self,event):
861        if not self.figmgr.toolbar.mode == '':
862            return
863        if event.button != 1:
864            return
865
866        try: self.lastspan
867        except AttributeError: pass
868        else:
869            self._remove_span(self.lastspan)
870            del self.lastspan
871        if event.inaxes == self._thisregion['axes']:
872            xdataend = event.xdata
873        else:
874            xdataend = self.xdataold
875
876        self._thisregion['worldx'][1] = xdataend
877        lregion = self._thisregion['worldx']
878        # WORKAROUND for the issue axvspan started to reset xlim.
879        axlimx = self._thisregion['axes'].get_xlim()
880        pregion = self._thisregion['axes'].axvspan(lregion[0],lregion[1],
881                                                   facecolor='0.7')
882        self._thisregion['axes'].set_xlim(axlimx)
883       
884        self.plotter._plotter.canvas.draw()
885        self._polygons.append(pregion)
886        srow = self._getrownum(self._thisregion['axes'])
887        irow = int(srow)
888        if not self._selregions.has_key(srow):
889            self._selregions[srow] = []
890        self._selregions[srow].append(lregion)
891        del lregion, pregion, xdataend
892        sout = "selected region: "+str(self._thisregion['worldx'])+\
893              "(@row "+str(self._getrownum(self._thisregion['axes']))+")"
894        asaplog.push(sout)
895
896        # release event
897        self.plotter._plotter.register('button_press',None)
898        self.plotter._plotter.register('motion_notify',None)
899        # Clear up region selection
900        self._thisregion = None
901        self.xdataold = None
902        self.xold = None
903        # finally recover region selection event
904        self.plotter._plotter.register('button_press',self._add_region)
905
906    ### add panels to selections
907    @asaplog_post_dec
908    def _add_panel(self,event):
909        if not self.figmgr.toolbar.mode == '':
910            return
911        if event.button != 1 or event.inaxes == None:
912            return
913        selax = event.inaxes
914        # this row resolution assumes row panelling
915        srow = self._getrownum(selax)
916        irow = int(srow)
917        if srow:
918            self._selpanels.append(irow)
919        shadow = Rectangle((0,0),1,1,facecolor='0.7',transform=selax.transAxes,visible=True)
920        self._polygons.append(selax.add_patch(shadow))
921        #self.plotter._plotter.show(False)
922        self.plotter._plotter.canvas.draw()
923        asaplog.push("row "+str(irow)+" is selected")
924        ## check for region selection of the spectra and overwrite it.
925        ##!!!! currently disabled for consistency with flag tools !!!!
926        #if self._selregions.has_key(srow):
927        #    self._selregions.pop(srow)
928        #    msg = "The whole spectrum is selected for row="+srow+". Region selection will be overwritten."
929        #    asaplog.push(msg)
930
931    def _getrownum(self,axis):
932        ### returns the row number of selected spectrum as a string ###
933        plabel = axis.get_title()
934        if plabel.startswith("row "):
935            return plabel.strip("row ")
936        return None
937
938    def _any_selection(self):
939        ### returns if users have selected any spectrum or region ###
940        if len(self._selpanels) or len(self._selregions):
941            return True
942        return False
943
944    def _plot_selections(self,regions=None,panels=None):
945        ### mark panels/spectra selections in the page
946        if not self._any_selection() and not (regions or panels):
947            return
948        regions = regions or self._selregions.copy() or {}
949        panels = panels or self._selpanels or []
950        if not isinstance(regions,dict):
951            asaplog.post()
952            asaplog.push("Invalid region specification")
953            asaplog.post('ERROR')
954        if not isinstance(panels,list):
955            asaplog.post()
956            asaplog.push("Invalid panel specification")
957            asaplog.post('ERROR')
958        strow = self._getrownum(self.plotter._plotter.subplots[0]['axes'])
959        enrow = self._getrownum(self.plotter._plotter.subplots[-1]['axes'])
960        for irow in range(int(strow),int(enrow)+1):
961            if regions.has_key(str(irow)):
962                ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
963                mlist = regions.pop(str(irow))
964                # WORKAROUND for the issue axvspan started to reset xlim.
965                axlimx = ax.get_xlim()
966                for i in range(len(mlist)):
967                    self._polygons.append(ax.axvspan(mlist[i][0],mlist[i][1],
968                                                     facecolor='0.7'))
969                ax.set_xlim(axlimx)
970                del ax,mlist,axlimx
971            if irow in panels:
972                ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
973                shadow = Rectangle((0,0),1,1,facecolor='0.7',
974                                   transform=ax.transAxes,visible=True)
975                self._polygons.append(ax.add_patch(shadow))
976                del ax,shadow
977        self.plotter._plotter.canvas.draw()
978        del regions,panels,strow,enrow
979
980    def _clear_selection_plot(self, refresh=True):
981        ### clear up polygons which mark selected spectra and regions ###
982        if len(self._polygons) > 0:
983            for shadow in self._polygons:
984                shadow.remove()
985            if refresh: self.plotter._plotter.canvas.draw()
986        self._polygons = []
987
988    def _clearup_selections(self, refresh=True):
989        # clear-up selection and polygons
990        self._selpanels = []
991        self._selregions = {}
992        self._clear_selection_plot(refresh=refresh)
993
994    ### clear up selections
995    def cancel_select(self):
996        self.figmgr.toolbar.set_message('selections canceled')
997        # clear-up selection and polygons
998        self._clearup_selections(refresh=True)
999
1000    ### flag selected spectra/regions
1001    @asaplog_post_dec
1002    def flag(self):
1003        if not self._any_selection():
1004            msg = "No selection to be Flagged"
1005            asaplog.post()
1006            asaplog.push(msg)
1007            asaplog.post('WARN')
1008            return
1009        self._pause_buttons(operation="start",msg="Flagging data...")
1010        self._flag_operation(rows=self._selpanels,
1011                             regions=self._selregions,unflag=False)
1012        sout = "Flagged:\n"
1013        sout += "  rows = "+str(self._selpanels)+"\n"
1014        sout += "  regions: "+str(self._selregions)
1015        asaplog.push(sout)
1016        del sout
1017        self.plotter._ismodified = True
1018        self._clearup_selections(refresh=False)
1019        self._plot_page(pagemode="current")
1020        self._pause_buttons(operation="end")
1021
1022    ### unflag selected spectra/regions
1023    @asaplog_post_dec
1024    def unflag(self):
1025        if not self._any_selection():
1026            msg = "No selection to be Flagged"
1027            asaplog.push(msg)
1028            asaplog.post('WARN')
1029            return
1030        self._pause_buttons(operation="start",msg="Unflagging data...")
1031        self._flag_operation(rows=self._selpanels,
1032                             regions=self._selregions,unflag=True)
1033        sout = "Unflagged:\n"
1034        sout += "  rows = "+str(self._selpanels)+"\n"
1035        sout += "  regions: "+str(self._selregions)
1036        asaplog.push(sout)
1037        del sout
1038        self.plotter._ismodified = True
1039        self._clearup_selections(refresh=False)
1040        self._plot_page(pagemode="current")
1041        self._pause_buttons(operation="end")
1042
1043    ### actual flag operation
1044    @asaplog_post_dec
1045    def _flag_operation(self,rows=None,regions=None,unflag=False):
1046        scan = self.plotter._data
1047        if not scan:
1048            asaplog.post()
1049            asaplog.push("Invalid scantable")
1050            asaplog.post("ERROR")
1051        if isinstance(rows,list) and len(rows) > 0:
1052            scan.flag_row(rows=rows,unflag=unflag)
1053        if isinstance(regions,dict) and len(regions) > 0:
1054            for srow, masklist in regions.iteritems():
1055                if not isinstance(masklist,list) or len(masklist) ==0:
1056                    msg = "Ignoring invalid region selection for row = "+srow
1057                    asaplog.post()
1058                    asaplog.push(msg)
1059                    asaplog.post("WARN")
1060                    continue
1061                irow = int(srow)
1062                mask = scan.create_mask(masklist,invert=False,row=irow)
1063                scan.flag(row=irow,mask=mask,unflag=unflag)
1064                del irow, mask
1065            del srow, masklist
1066        del scan
1067
1068    ### show statistics of selected spectra/regions
1069    @asaplog_post_dec
1070    def stat_cal(self):
1071        if not self._any_selection():
1072            msg = "No selection to be calculated"
1073            asaplog.push(msg)
1074            asaplog.post('WARN')
1075            return
1076        self._selected_stats(rows=self._selpanels,regions=self._selregions)
1077        self._clearup_selections(refresh=True)
1078
1079    @asaplog_post_dec
1080    def _selected_stats(self,rows=None,regions=None):
1081        scan = self.plotter._data
1082        if not scan:
1083            asaplog.post()
1084            asaplog.push("Invalid scantable")
1085            asaplog.post("ERROR")
1086        mathobj = stmath( rcParams['insitu'] )
1087        statval = {}
1088        statstr = ['max', 'min', 'mean', 'median', 'sum', 'stddev', 'rms']
1089        if isinstance(rows, list) and len(rows) > 0:
1090            for irow in rows:
1091                for stat in statstr:
1092                    statval[stat] = mathobj._statsrow(scan,[],stat,irow)[0]
1093                self._print_stats(scan,irow,statval,statstr=statstr)
1094            del irow
1095        if isinstance(regions,dict) and len(regions) > 0:
1096            for srow, masklist in regions.iteritems():
1097                if not isinstance(masklist,list) or len(masklist) ==0:
1098                    msg = "Ignoring invalid region selection for row = "+srow
1099                    asaplog.post()
1100                    asaplog.push(msg)
1101                    asaplog.post("WARN")
1102                    continue
1103                irow = int(srow)
1104                mask = scan.create_mask(masklist,invert=False,row=irow)
1105                for stat in statstr:
1106                    statval[stat] = mathobj._statsrow(scan,mask,stat,irow)[0]
1107                self._print_stats(scan,irow,statval,statstr=statstr,
1108                                  mask=masklist)
1109                del irow, mask
1110            del srow, masklist
1111        del scan, statval, mathobj
1112
1113    @asaplog_post_dec
1114    def _print_stats(self,scan,row,stats,statstr=None,mask=None):
1115        if not isinstance(scan, scantable):
1116            asaplog.post()
1117            asaplog.push("Invalid scantable")
1118            asaplog.post("ERROR")
1119        if row < 0 or row > scan.nrow():
1120            asaplog.post()
1121            asaplog.push("Invalid row number")
1122            asaplog.post("ERROR")
1123        if not isinstance(stats,dict) or len(stats) == 0:
1124            asaplog.post()
1125            asaplog.push("Invalid statistic value")
1126            asaplog.post("ERROR")
1127        maskstr = "All"
1128        if mask:
1129            maskstr = str(mask)
1130        ssep = "-"*70+"\n"
1131        sout = ssep
1132        sout += ("Row=%d  Scan=%d  IF=%d  Pol=%d  Time=%s  mask=%s" % \
1133                 (row, scan.getscan(row), scan.getif(row), scan.getpol(row), scan.get_time(row),maskstr))
1134        sout += "\n"
1135        statvals = []
1136        if not len(statstr):
1137            statstr = stats.keys()
1138        for key in statstr:
1139            sout += key.ljust(10)
1140            statvals.append(stats.pop(key))
1141        sout += "\n"
1142        sout += ("%f "*len(statstr) % tuple(statvals))
1143        sout += "\n"+ssep
1144        asaplog.push(sout)
1145        del sout, ssep, maskstr, statvals, key, scan, row, stats, statstr, mask
1146
1147    ### Page chages
1148    ### go to the previous page
1149    def prev_page(self):
1150        self._pause_buttons(operation="start",msg='plotting the previous page')
1151        self._clear_selection_plot(refresh=False)
1152        self._plot_page(pagemode="prev")
1153        self._plot_selections()
1154        self._pause_buttons(operation="end")
1155
1156    ### go to the next page
1157    def next_page(self):
1158        self._pause_buttons(operation="start",msg='plotting the next page')
1159        self._clear_selection_plot(refresh=False)
1160        self._plot_page(pagemode="next")
1161        self._plot_selections()
1162        self._pause_buttons(operation="end")
1163
1164    ### actual plotting of the new page
1165    def _plot_page(self,pagemode="next"):
1166        if self.plotter._startrow <= 0:
1167            msg = "The page counter is reset due to chages of plot settings. "
1168            msg += "Plotting from the first page."
1169            asaplog.post()
1170            asaplog.push(msg)
1171            asaplog.post('WARN')
1172            goback = False
1173
1174        self.plotter._plotter.hold()
1175        #self.plotter._plotter.legend(1)
1176        self._set_plot_counter(pagemode)
1177        self.plotter._plot(self.plotter._data)
1178        self.set_pagecounter(self._get_pagenum())
1179        self.plotter._plotter.release()
1180        self.plotter._plotter.tidy()
1181        self.plotter._plotter.show(hardrefresh=False)
1182
1183    ### calculate the panel ID and start row to plot a page
1184    #def _set_prevpage_counter(self):
1185    def _set_plot_counter(self, pagemode):
1186        ## page operation should be either "previous", "current", or "next"
1187        availpage = ["p","c","n"]
1188        pageop = pagemode[0].lower()
1189        if not (pageop in availpage):
1190            asaplog.post()
1191            asaplog.push("Invalid page operation")
1192            asaplog.post("ERROR")
1193        if pageop == "n":
1194            # nothing necessary to plot the next page
1195            return
1196        # set row and panel counters to those of the 1st panel of previous page
1197        maxpanel = 25
1198        # the ID of the last panel in current plot
1199        lastpanel = self.plotter._ipanel
1200        # the number of current subplots
1201        currpnum = len(self.plotter._plotter.subplots)
1202
1203        # the nuber of previous subplots
1204        start_ipanel = None
1205        if pageop == "c":
1206            start_ipanel = max(lastpanel-currpnum+1, 0)
1207        else:
1208            ## previous page
1209            prevpnum = None
1210            if self.plotter._rows and self.plotter._cols:
1211                # when user set layout
1212                prevpnum = self.plotter._rows*self.plotter._cols
1213            else:
1214                # no user specification
1215                prevpnum = maxpanel
1216            start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
1217            del prevpnum
1218
1219        # set the pannel ID of the last panel of the prev(-prev) page
1220        self.plotter._ipanel = start_ipanel-1
1221        if self.plotter._panelling == 'r':
1222            self.plotter._startrow = start_ipanel
1223        else:
1224            # the start row number of the next panel
1225            self.plotter._startrow = self.plotter._panelrows[start_ipanel]
1226        del lastpanel,currpnum,start_ipanel
1227
1228    ### refresh the page counter
1229    def set_pagecounter(self,page):
1230        nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
1231        nwidth = max(nwidth,4)
1232        formatstr = '%'+str(nwidth)+'d'
1233        self.show_pagenum(page,formatstr)
1234
1235    def show_pagenum(self,pagenum,formatstr):
1236        # passed to backend dependent class
1237        pass
1238
1239    def _get_pagenum(self):
1240        # get the ID of last panel in the current page
1241        idlastpanel = self.plotter._ipanel
1242        # max panels in a page
1243        ppp = self.plotter._plotter.rows*self.plotter._plotter.cols
1244        return int(idlastpanel/ppp)+1
1245
1246    # pause buttons for slow operations. implemented at a backend dependent class
1247    def _pause_buttons(self,operation="end",msg=""):
1248        pass
Note: See TracBrowser for help on using the repository browser.