source: trunk/python/customgui_base.py @ 2155

Last change on this file since 2155 was 2155, checked in by Kana Sugimoto, 13 years ago

New Development: Yes

JIRA Issue: No (reorganization of modules)

Ready for Test: Yes

Interface Changes: Yes

What Interface Changed: the modules casatoolbar, flagtoolbar, notaionwindow, were reorganized as customgui_base, customgui_tkagg, and customgui_qt4agg, based on backend

Test Programs: List test programs

Put in Release Notes: Yes/No?

Module(s): Module Names change impacts.

Description: Describe your changes here...


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