source: trunk/python/customgui_base.py @ 2796

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

New Development: No

JIRA Issue: Yes (CAS-4859)

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs:

set asap.rcParams[scantable.storage] = 'disk'
run asap.plotter.plot
exit from casapy or asap. A temporary scantable on disk should be deleted.

Put in Release Notes: No

Module(s): asap.plotter, sdplot

Description:

There were cyclic references between plotters and toolbars, i.e., plotter and CustomToolbarCommon?,
and flagplotter and CustomFlagToolbarCommon?, which prevented deletion of temporary scantables
from disk when exitting sessions.
I changed the reference to plotters in toolbars as a weak reference so that cyclic referencing
should not avoid desctruction of the plotters. It should be ok because there shouldn't be a case
where toolbar exists without plotter, but I'm not 100% sure to be honest.


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