source: trunk/python/customgui_base.py

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

New Development: No (a bug fix)

JIRA Issue: Yes (CAS-4859)

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs:

Put in Release Notes: No

Module(s): asap.asapplotter and sdplot

Description:

Fixed a bug in the usage of weakref.


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