source: trunk/python/customgui_base.py @ 2172

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

New Development: No

JIRA Issue: Yes (CAS-2963/ATNF-240)

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs: comare statistic result of interactive operation with command line none

Put in Release Notes: No

Module(s): asapplotter, sdplot

Description:

statistic mode in ASAP plotter now prints statistics (max, min, median, mean, sum, std dev) of
spectra only in the selected subplot.


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