casa  $Rev:20696$
 All Classes Namespaces Files Functions Variables
customgui_base.py
Go to the documentation of this file.
00001 import os
00002 import matplotlib, numpy
00003 from asap.logging import asaplog, asaplog_post_dec
00004 from matplotlib.patches import Rectangle
00005 from asap.parameters import rcParams
00006 from asap import scantable
00007 from asap._asap import stmath
00008 from asap.utils import _n_bools, mask_not, mask_or
00009 
00010 ######################################
00011 ##    Add CASA custom toolbar       ##
00012 ######################################
00013 class CustomToolbarCommon:
00014     def __init__(self,parent):
00015         self.plotter = parent
00016         #self.figmgr=self.plotter._plotter.figmgr
00017 
00018     ### select the nearest spectrum in pick radius
00019     ###    and display spectral value on the toolbar.
00020     def _select_spectrum(self,event):
00021         # Do not fire event when in zooming/panning mode
00022         mode = self.figmgr.toolbar.mode
00023         if not mode == '':
00024             return
00025             # When selected point is out of panels
00026         if event.inaxes == None:
00027             return
00028         # If not left button
00029         if event.button != 1:
00030             return
00031 
00032         xclick = event.xdata
00033         yclick = event.ydata
00034         dist2 = 1000.
00035         pickline = None
00036         # If the pannel has picable objects
00037         pflag = False
00038         for lin in event.inaxes.lines:
00039             if not lin.pickable():
00040                 continue
00041             pflag = True
00042             flag,pind = lin.contains(event)
00043             if not flag:
00044                 continue
00045             # Get nearest point
00046             inds = pind['ind']
00047             xlin = lin.get_xdata()
00048             ylin = lin.get_ydata()
00049             for i in inds:
00050                 d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
00051                 if dist2 >= d2:
00052                     dist2 = d2
00053                     pickline = lin
00054         # No pickcable line in the pannel
00055         if not pflag:
00056             return
00057         # Pickable but too far from mouse position
00058         elif pickline is None:
00059             picked = 'No line selected.'
00060             self.figmgr.toolbar.set_message(picked)
00061             return
00062         del pind, inds, xlin, ylin
00063         # Spectra are Picked
00064         theplot = self.plotter._plotter
00065         thetoolbar = self.figmgr.toolbar
00066         thecanvas = self.figmgr.canvas
00067         # Disconnect the default motion notify event
00068         # Notice! the other buttons are also diabled!!!
00069         thecanvas.mpl_disconnect(thetoolbar._idDrag)
00070         # Get picked spectrum
00071         xdata = pickline.get_xdata()
00072         ydata = pickline.get_ydata()
00073         titl = pickline.get_label()
00074         titp = event.inaxes.title.get_text()
00075         panel0 = event.inaxes
00076         picked = "Selected: '"+titl+"' in panel '"+titp+"'."
00077         thetoolbar.set_message(picked)
00078         # Generate a navigation window
00079         #naviwin=Navigationwindow(titp,titl)
00080         #------------------------------------------------------#
00081         # Show spectrum data at mouse position
00082         def spec_data(event):
00083             # Getting spectrum data of neiboring point
00084             xclick = event.xdata
00085             if event.inaxes != panel0:
00086                 return
00087             ipoint = len(xdata)-1
00088             for i in range(len(xdata)-1):
00089                 xl = xclick - xdata[i]
00090                 xr = xclick - xdata[i+1]
00091                 if xl*xr <= 0.:
00092                     ipoint = i
00093                     break
00094             # Output spectral value on the navigation window
00095             posi = '[ %s, %s ]:  x = %.2f   value = %.2f'\
00096                    %(titl,titp,xdata[ipoint],ydata[ipoint])
00097             #naviwin.posi.set(posi)
00098             thetoolbar.set_message(posi)
00099         #------------------------------------------------------#
00100         # Disconnect from mouse events
00101         def discon(event):
00102             #naviwin.window.destroy()
00103             theplot.register('motion_notify',None)
00104             # Re-activate the default motion_notify_event
00105             thetoolbar._idDrag = thecanvas.mpl_connect('motion_notify_event',
00106                                                        thetoolbar.mouse_move)
00107             theplot.register('button_release',None)
00108             return
00109         #------------------------------------------------------#
00110         # Show data value along with mouse movement
00111         theplot.register('motion_notify',spec_data)
00112         # Finish events when mouse button is released
00113         theplot.register('button_release',discon)
00114 
00115 
00116     ### Notation
00117     def _mod_note(self,event):
00118         # Do not fire event when in zooming/panning mode
00119         if not self.figmgr.toolbar.mode == '':
00120             return
00121         if event.button == 1:
00122             self.notewin.load_textwindow(event)
00123         elif event.button == 3 and self._note_picked(event):
00124             self.notewin.load_modmenu(event)
00125         return
00126 
00127     def _note_picked(self,event):
00128         # just briefly check if any texts are picked
00129         for textobj in self.canvas.figure.texts:
00130             if textobj.contains(event)[0]:
00131                 return True
00132         for ax in self.canvas.figure.axes:
00133             for textobj in ax.texts:
00134                 if textobj.contains(event)[0]:
00135                     return True
00136         #print "No text picked"
00137         return False
00138 
00139 
00140     ### Purely plotter based statistics calculation of a selected area.
00141     ### No access to scantable
00142     def _single_mask(self,event):
00143         # Do not fire event when in zooming/panning mode
00144         if not self.figmgr.toolbar.mode == '':
00145             return
00146         # When selected point is out of panels
00147         if event.inaxes == None:
00148             return
00149         if event.button == 1:
00150             exclude=False
00151         elif event.button == 3:
00152             exclude=True
00153         else:
00154             return
00155 
00156         self._thisregion = {'axes': event.inaxes,'xs': event.x,
00157                             'worldx': [event.xdata,event.xdata],
00158                             'invert': exclude}
00159         self.xold = event.x
00160         self.xdataold = event.xdata
00161 
00162         self.plotter._plotter.register('button_press',None)
00163         self.plotter._plotter.register('motion_notify', self._xspan_draw)
00164         self.plotter._plotter.register('button_press', self._xspan_end)
00165 
00166     def _xspan_draw(self,event):
00167         if event.inaxes == self._thisregion['axes']:
00168             xnow = event.x
00169             self.xold = xnow
00170             self.xdataold = event.xdata
00171         else:
00172             xnow = self.xold
00173         try: self.lastspan
00174         except AttributeError: pass
00175         else:
00176             if self.lastspan: self._remove_span(self.lastspan)
00177 
00178         self.lastspan = self._draw_span(self._thisregion['axes'],
00179                                         self._thisregion['xs'], xnow, fill="")
00180         del xnow
00181 
00182     def _draw_span(self,axes,x0,x1,**kwargs):
00183         pass
00184 
00185     def _remove_span(self,span):
00186         pass
00187 
00188     @asaplog_post_dec
00189     def _xspan_end(self,event):
00190         if not self.figmgr.toolbar.mode == '':
00191             return
00192         #if event.button != 1:
00193         #    return
00194 
00195         try: self.lastspan
00196         except AttributeError: pass
00197         else:
00198             self._remove_span(self.lastspan)
00199             del self.lastspan
00200         if event.inaxes == self._thisregion['axes']:
00201             xdataend = event.xdata
00202         else:
00203             xdataend = self.xdataold
00204 
00205         self._thisregion['worldx'][1] = xdataend
00206         # print statistics of spectra in subplot
00207         self._subplot_stats(self._thisregion)
00208         
00209         # release event
00210         self.plotter._plotter.register('button_press',None)
00211         self.plotter._plotter.register('motion_notify',None)
00212         # Clear up region selection
00213         self._thisregion = None
00214         self.xdataold = None
00215         self.xold = None
00216         # finally recover region selection event
00217         self.plotter._plotter.register('button_press',self._single_mask)
00218 
00219     def _subplot_stats(self,selection):
00220         statstr = ['max', 'min', 'median', 'mean', 'sum', 'std'] #'rms']
00221         panelstr = selection['axes'].title.get_text()
00222         ssep = "-"*70
00223         asaplog.push(ssep)
00224         asaplog.post()
00225         for line in selection['axes'].lines:
00226             # Don't include annotations
00227             if line.get_label().startswith("_"):
00228                 continue
00229             label = panelstr + ", "+line.get_label()
00230             x = line.get_xdata()
00231             newmsk = None
00232             selmsk = self._create_flag_from_array(x,
00233                                                   selection['worldx'],
00234                                                   selection['invert'])
00235             ydat = None
00236             y = line.get_ydata()
00237             if numpy.ma.isMaskedArray(y):
00238                 ydat = y.data
00239                 basemsk = y.mask
00240             else:
00241                 ydat = y
00242                 basemsk = False
00243             if not isinstance(basemsk, bool):
00244                 # should be ndarray
00245                 newmsk = mask_or(selmsk, basemsk)
00246             elif basemsk:
00247                 # the whole original spectrum is flagged
00248                 newmsk = basemsk
00249             else:
00250                 # no channel was flagged originally
00251                 newmsk = selmsk
00252             mdata = numpy.ma.masked_array(ydat, mask=newmsk)
00253             statval = {}
00254             for stat in statstr:
00255                 # need to get the stat functions from the ma module!!!
00256                 statval[stat] = getattr(numpy.ma,stat)(mdata)
00257             self._print_stats(statval, statstr=statstr, label=label,\
00258                               mask=selection['worldx'],\
00259                               unmask=selection['invert'])
00260             asaplog.push(ssep)
00261             asaplog.post()
00262 
00263     def _create_flag_from_array(self,x,masklist,invert):
00264         # Return True for channels which should be EXCLUDED (flag)
00265         if len(masklist) <= 1:
00266             asaplog.push()
00267             asaplog.post("masklist should be a list of 2 elements")
00268             asaplog.push("ERROR")
00269         n = len(x)
00270         # Base mask: flag out all channels
00271         mask = _n_bools(n, True)
00272         minval = min(masklist[0:2])
00273         maxval = max(masklist[0:2])
00274         for i in range(n):
00275             if minval <= x[i] <= maxval:
00276                 mask[i] = False
00277         if invert:
00278             mask = mask_not(mask)
00279         return mask
00280 
00281     @asaplog_post_dec
00282     def _print_stats(self,stats,statstr=None,label="",mask=None,unmask=False):
00283         if not isinstance(stats,dict) or len(stats) == 0:
00284             asaplog.post()
00285             asaplog.push("Invalid statistic value")
00286             asaplog.post("ERROR")
00287         maskstr = "Not available"
00288         if mask:
00289             masktype = "mask"
00290             maskstr = str(mask)
00291             if unmask: masktype = "unmask"
00292 
00293         sout = label + ", " + masktype + " = " + maskstr + "\n"
00294         statvals = []
00295         if not len(statstr):
00296             statstr = stats.keys()
00297         for key in statstr:
00298             sout += key.ljust(10)
00299             statvals.append(stats.pop(key))
00300         sout += "\n"
00301         sout += ("%f "*len(statstr) % tuple(statvals))
00302         asaplog.push(sout)
00303         #del sout, maskstr, masktype, statvals, key, stats, statstr, mask, label
00304 
00305 
00306     ### Page chages
00307     ### go to the previous page
00308     def prev_page(self):
00309         self.figmgr.toolbar.set_message('plotting the previous page')
00310         #self._pause_buttons(operation="start",msg='plotting the previous page')
00311         self._new_page(goback=True)
00312         #self._pause_buttons(operation="end")
00313 
00314     ### go to the next page
00315     def next_page(self):
00316         self.figmgr.toolbar.set_message('plotting the next page')
00317         #self._pause_buttons(operation="start",msg='plotting the next page')
00318         self._new_page(goback=False)
00319         #self._pause_buttons(operation="end")
00320 
00321     ### actual plotting of the new page
00322     def _new_page(self,goback=False):
00323         top = None
00324         header = self.plotter._headtext
00325         reset = False
00326         doheader = (isinstance(header['textobj'],list) and \
00327                     len(header['textobj']) > 0)
00328         if self.plotter._startrow <= 0:
00329             msg = "The page counter is reset due to chages of plot settings. "
00330             msg += "Plotting from the first page."
00331             asaplog.push(msg)
00332             asaplog.post('WARN')
00333             reset = True
00334             goback = False
00335             if doheader:
00336                 extrastr = selstr = ''
00337                 if header.has_key('extrastr'):
00338                     extrastr = header['extrastr']
00339                 if header.has_key('selstr'):
00340                     selstr = header['selstr']
00341             self.plotter._reset_header()
00342         if doheader:
00343             top = self.plotter._plotter.figure.subplotpars.top
00344             fontsize = header['textobj'][0].get_fontproperties().get_size()
00345 
00346         self.plotter._plotter.hold()
00347         if goback:
00348             self._set_prevpage_counter()
00349         #self.plotter._plotter.clear()
00350         self.plotter._plot(self.plotter._data)
00351         self.set_pagecounter(self._get_pagenum())
00352         # Plot header information
00353         if header['textobj']:
00354             if top and top != self.plotter._margins[3]:
00355                 # work around for sdplot in CASA. complete checking in future?
00356                 self.plotter._plotter.figure.subplots_adjust(top=top)
00357             if reset:
00358                 self.plotter.print_header(plot=True,fontsize=fontsize,selstr=selstr, extrastr=extrastr)
00359             else:
00360                 self.plotter._header_plot(header['string'],fontsize=fontsize)
00361         self.plotter._plotter.release()
00362         self.plotter._plotter.tidy()
00363         self.plotter._plotter.show(hardrefresh=False)
00364         del top
00365 
00366     ### calculate the panel ID and start row to plot the previous page
00367     def _set_prevpage_counter(self):
00368         # set row and panel counters to those of the 1st panel of previous page
00369         maxpanel = 16
00370         # the ID of the last panel in current plot
00371         lastpanel = self.plotter._ipanel
00372         # the number of current subplots
00373         currpnum = len(self.plotter._plotter.subplots)
00374         # the nuber of previous subplots
00375         prevpnum = None
00376         if self.plotter._rows and self.plotter._cols:
00377             # when user set layout
00378             prevpnum = self.plotter._rows*self.plotter._cols
00379         else:
00380             # no user specification
00381             prevpnum = maxpanel
00382 
00383         start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
00384         # set the pannel ID of the last panel of prev-prev page
00385         self.plotter._ipanel = start_ipanel-1
00386         if self.plotter._panelling == 'r':
00387             self.plotter._startrow = start_ipanel
00388         else:
00389             # the start row number of the next panel
00390             self.plotter._startrow = self.plotter._panelrows[start_ipanel]
00391         del lastpanel,currpnum,prevpnum,start_ipanel
00392 
00393     ### refresh the page counter
00394     def set_pagecounter(self,page):
00395         nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
00396         nwidth = max(nwidth,4)
00397         formatstr = '%'+str(nwidth)+'d'
00398         self.show_pagenum(page,formatstr)
00399 
00400     def show_pagenum(self,pagenum,formatstr):
00401         # passed to backend dependent class
00402         pass        
00403 
00404     def _get_pagenum(self):
00405         maxpanel = 16
00406         # get the ID of last panel in the current page
00407         idlastpanel = self.plotter._ipanel
00408         if self.plotter._rows and self.plotter._cols:
00409             ppp = self.plotter._rows*self.plotter._cols
00410         else:
00411             ppp = maxpanel
00412         return int(idlastpanel/ppp)+1
00413 
00414     # pause buttons for slow operations. implemented at a backend dependent class
00415     def _pause_buttons(self,operation="end",msg=""):
00416         pass
00417 
00418 
00419 
00420 
00421 ######################################
00422 ##    Notation box window           ##
00423 ######################################
00424 class NotationWindowCommon:
00425     """
00426     A base class to define the functions that the backend-based
00427     GUI notation class must implement to print/modify/delete notes on a canvas.
00428 
00429     The following methods *must* be implemented in the backend-based
00430     parent class:
00431         _get_note : get text in text box
00432         _get_anchval : get anchor value selected
00433     """
00434     def __init__(self,master=None):
00435         #self.parent = master
00436         self.canvas = master
00437         self.event = None
00438         self.note = None
00439         self.anchors = ["figure","axes","data"]
00440         self.seltext = {}
00441         self.numnote = 0
00442 
00443     @asaplog_post_dec
00444     def print_text(self):
00445         """
00446         Print a note on a canvas specified with the Notation window.
00447         Called when 'print' button selected on the window.
00448         """
00449         anchor = self.anchors[self._get_anchval()]
00450         notestr = self._get_note().rstrip("\n")
00451         if len(notestr.strip()) == 0:
00452             #self._clear_textbox()
00453             #print "Empty string!"
00454             return
00455 
00456         myaxes = None
00457         calcpos = True
00458         xpos = None
00459         ypos = None
00460         if self.seltext:
00461             # You are modifying a text
00462             mycanvas = self.canvas
00463             oldanch = self.seltext['anchor']
00464             if oldanch != 'figure':
00465                 myaxes = self.seltext['parent']
00466             calcpos = (anchor != oldanch)
00467             if not calcpos:
00468                 # printing text in the same coord.
00469                 # you don't have to recalc position
00470                 parent = self.seltext['parent']
00471                 transform = self.seltext['textobj'].get_transform()
00472                 (xpos, ypos) = self.seltext['textobj'].get_position()
00473             elif anchor == "figure":
00474                 # converting from "axes"/"data" -> "figure"
00475                 (x, y) = self.seltext['textobj']._get_xy_display()
00476             elif oldanch == "data":
00477                 # converting from "data" -> "axes".
00478                 # need xdata & ydata in the axes
00479                 (x, y) = self.seltext['textobj'].get_position()
00480             else:
00481                 # converting "figure"/"axes" -> "data"
00482                 # need to calculate xdata & ydata in the axes
00483                 pixpos = self.seltext['textobj']._get_xy_display()
00484                 (w,h) = mycanvas.get_width_height()
00485                 relpos = (pixpos[0]/float(w), pixpos[1]/float(h))
00486                 if not myaxes:
00487                     myaxes = self._get_axes_from_pos(relpos,mycanvas)
00488                     if not myaxes:
00489                         raise RuntimeError, "Axes resolution failed!"
00490                 (x, y) = self._convert_pix2dat(relpos,myaxes)
00491             self._remove_seltext()
00492         elif self.event:
00493             mycanvas = self.event.canvas
00494             myaxes = self.event.inaxes
00495             if myaxes and (anchor != "figure"):
00496                 x = self.event.xdata
00497                 y = self.event.ydata
00498             else:
00499                 x = self.event.x
00500                 y = self.event.y
00501         else:
00502             raise RuntimeError, "No valid position to print data"
00503             return
00504 
00505         # now you know 
00506         picker = True
00507         # alignment of the text: ha (horizontal), va (vertical)
00508         ha = 'left'
00509         #va = 'center'
00510         va = 'top'
00511         if not calcpos:
00512             # you aready know parent, tansform, xpos and ypos
00513             pass
00514         elif anchor == "figure":
00515             # text instance will be appended to mycanvas.figure.texts
00516             parent = mycanvas.figure
00517             transform = parent.transFigure
00518             (w,h) = mycanvas.get_width_height()
00519             xpos = x/float(w)
00520             ypos = y/float(h)            
00521         elif myaxes:
00522             ## text instance will be appended to myaxes.texts
00523             parent = myaxes
00524             if anchor == "axes":
00525                 transform = myaxes.transAxes
00526                 lims = myaxes.get_xlim()
00527                 xpos = (x-lims[0])/(lims[1]-lims[0])
00528                 lims = myaxes.get_ylim()
00529                 ypos = (y-lims[0])/(lims[1]-lims[0])
00530             else:
00531                 # anchored on "data"
00532                 transform = myaxes.transData
00533                 xpos = x
00534                 ypos = y
00535         parent.text(xpos,ypos,notestr,transform=transform,
00536                     ha=ha,va=va,picker=picker)
00537         mycanvas.draw()
00538 
00539         self.numnote += 1
00540 
00541         #self._clear_textbox()
00542         msg = "Added note: '"+notestr+"'"
00543         msg += " @["+str(xpos)+", "+str(ypos)+"] ("+anchor+"-coord)"
00544         msg += "\ntotal number of notes are "+str(self.numnote)
00545         asaplog.push( msg )
00546 
00547     def _get_axes_from_pos(self,pos,canvas):
00548         """helper function to get axes of a position in a plot (fig-coord)"""
00549         if len(pos) != 2:
00550             raise ValueError, "pixel position should have 2 elements"
00551         for axes in canvas.figure.axes:
00552             ##check if pos is in the axes
00553             #if axes.contains_point(pos): ### seems not working
00554             #    return axes
00555             try:
00556                 axbox = axes.get_position().get_points()
00557             except AttributeError: ### WORKAROUND for old matplotlib
00558                 axbox = self._oldpos2new(axes.get_position())
00559             if (axbox[0][0] <= pos[0] <= axbox[1][0]) and \
00560                (axbox[0][1] <= pos[1] <= axbox[1][1]):
00561                 return axes
00562         return None
00563 
00564     ### WORKAROUND for old matplotlib
00565     def _oldpos2new(self,oldpos=None):
00566         return [[oldpos[0],oldpos[1]],[oldpos[0]+oldpos[2],oldpos[1]+oldpos[3]]]
00567         
00568     def _convert_pix2dat(self,pos,axes):
00569         """
00570         helper function to convert a position in figure-coord (0-1) to
00571         data-coordinate of the axes        
00572         """
00573         # convert a relative position from lower-left of the canvas
00574         # to a data in axes
00575         if len(pos) != 2:
00576             raise ValueError, "pixel position should have 2 elements"
00577         # left-/bottom-pixel, and pixel width & height of the axes
00578         bbox = axes.get_position()
00579         try: 
00580             axpos = bbox.get_points()
00581         except AttributeError: ### WORKAROUND for old matplotlib
00582             axpos = self._oldpos2new(bbox)
00583         # check pos value
00584         if (pos[0] < axpos[0][0]) or (pos[1] < axpos[0][1]) \
00585                or (pos[0] > axpos[1][0]) or (pos[1] > axpos[1][1]):
00586             raise ValueError, "The position is out of the axes"
00587         xlims = axes.get_xlim()
00588         ylims = axes.get_ylim()
00589         wdat = xlims[1] - xlims[0]
00590         hdat = ylims[1] - ylims[0]
00591         xdat = xlims[0] + wdat*(pos[0] - axpos[0][0])/(axpos[1][0] - axpos[0][0])
00592         ydat = ylims[0] + hdat*(pos[1] - axpos[0][1])/(axpos[1][1] - axpos[0][1])
00593         return (xdat, ydat)
00594 
00595     @asaplog_post_dec
00596     def _get_selected_text(self,event):
00597         """helper function to return a dictionary of the nearest note to the event."""
00598         (w,h) = event.canvas.get_width_height()
00599         dist2 = w*w+h*h
00600         selected = {}
00601         for textobj in self.canvas.figure.texts:
00602             if textobj.contains(event)[0]:
00603                 d2 = self._get_text_dist2(event,textobj)
00604                 if dist2 >= d2:
00605                     dist2 = d2
00606                     selected = {'anchor': 'figure', \
00607                                 'parent': event.canvas.figure, 'textobj': textobj}
00608                     msg = "Fig loop: a text, '"+textobj.get_text()+"', at "
00609                     msg += str(textobj.get_position())+" detected"
00610                     #print msg
00611         for ax in self.canvas.figure.axes:
00612             for textobj in ax.texts:
00613                 if textobj.contains(event)[0]:
00614                     d2 = self._get_text_dist2(event,textobj)
00615                     if dist2 >= d2:
00616                         anchor='axes'
00617                         if ax.transData == textobj.get_transform():
00618                             anchor = 'data'                    
00619                         selected = {'anchor': anchor, \
00620                                     'parent': ax, 'textobj': textobj}
00621                         msg = "Ax loop: a text, '"+textobj.get_text()+"', at "
00622                         msg += str(textobj.get_position())+" detected"
00623                         #print msg 
00624 
00625         if selected:
00626             msg = "Selected (modify/delete): '"+selected['textobj'].get_text()
00627             msg += "' @"+str(selected['textobj'].get_position())
00628             msg += " ("+selected['anchor']+"-coord)"
00629             asaplog.push(msg)
00630 
00631         return selected
00632 
00633     def _get_text_dist2(self,event,textobj):
00634         """
00635         helper function to calculate square of distance between
00636         a event position and a text object. 
00637         """
00638         (x,y) = textobj._get_xy_display()
00639         return (x-event.x)**2+(y-event.y)**2
00640 
00641     def delete_note(self):
00642         """
00643         Remove selected note.
00644         """
00645         #print "You selected 'OK'"
00646         self._remove_seltext()
00647         self.canvas.draw()
00648 
00649     @asaplog_post_dec
00650     def _remove_seltext(self):
00651         """helper function to remove the selected note"""
00652         if len(self.seltext) < 3:
00653             raise ValueError, "Don't under stand selected text obj."
00654             return
00655         try:
00656             self.seltext['textobj'].remove()
00657         except NotImplementedError:
00658                 self.seltext['parent'].texts.pop(self.seltext['parent'].texts.index(self.seltext['textobj']))
00659         self.numnote -= 1
00660 
00661         textobj = self.seltext['textobj']
00662         msg = "Deleted note: '"+textobj.get_text()+"'"
00663         msg += "@"+str(textobj.get_position())\
00664                +" ("+self.seltext['anchor']+"-coord)"
00665         msg += "\ntotal number of notes are "+str(self.numnote)
00666         asaplog.push( msg )
00667 
00668         self.seltext = {}
00669 
00670     @asaplog_post_dec
00671     def cancel_delete(self):
00672         """
00673         Cancel deleting the selected note.
00674         Called when 'cancel' button selected on confirmation dialog.
00675         """
00676         asaplog.push( "Cancel deleting: '"+self.seltext['textobj'].get_text()+"'" )
00677         self.seltext = {}
00678 
00679 
00680 
00681 ###########################################
00682 ##    Add CASA custom Flag toolbar       ##
00683 ###########################################
00684 class CustomFlagToolbarCommon:
00685     def __init__(self,parent):
00686         self.plotter=parent
00687         #self.figmgr=self.plotter._plotter.figmgr
00688         self._selregions = {}
00689         self._selpanels = []
00690         self._polygons = []
00691         self._thisregion = None
00692         self.xdataold=None
00693 
00694     ### select the nearest spectrum in pick radius
00695     ###    and display spectral value on the toolbar.
00696     def _select_spectrum(self,event):
00697         # Do not fire event when in zooming/panning mode
00698         mode = self.figmgr.toolbar.mode
00699         if not mode == '':
00700             return
00701             # When selected point is out of panels
00702         if event.inaxes == None:
00703             return
00704         # If not left button
00705         if event.button != 1:
00706             return
00707 
00708         xclick = event.xdata
00709         yclick = event.ydata
00710         dist2 = 1000.
00711         pickline = None
00712         # If the pannel has picable objects
00713         pflag = False
00714         for lin in event.inaxes.lines:
00715             if not lin.pickable():
00716                 continue
00717             pflag = True
00718             flag,pind = lin.contains(event)
00719             if not flag:
00720                 continue
00721             # Get nearest point
00722             inds = pind['ind']
00723             xlin = lin.get_xdata()
00724             ylin = lin.get_ydata()
00725             for i in inds:
00726                 d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
00727                 if dist2 >= d2:
00728                     dist2 = d2
00729                     pickline = lin
00730         # No pickcable line in the pannel
00731         if not pflag:
00732             return
00733         # Pickable but too far from mouse position
00734         elif pickline is None:
00735             picked = 'No line selected.'
00736             self.figmgr.toolbar.set_message(picked)
00737             return
00738         del pind, inds, xlin, ylin
00739         # Spectra are Picked
00740         theplot = self.plotter._plotter
00741         thetoolbar = self.figmgr.toolbar
00742         thecanvas = self.figmgr.canvas
00743         # Disconnect the default motion notify event
00744         # Notice! the other buttons are also diabled!!!
00745         thecanvas.mpl_disconnect(thetoolbar._idDrag)
00746         # Get picked spectrum
00747         xdata = pickline.get_xdata()
00748         ydata = pickline.get_ydata()
00749         titl = pickline.get_label()
00750         titp = event.inaxes.title.get_text()
00751         panel0 = event.inaxes
00752         picked = "Selected: '"+titl+"' in panel '"+titp+"'."
00753         thetoolbar.set_message(picked)
00754         # Generate a navigation window
00755         #naviwin=Navigationwindow(titp,titl)
00756         #------------------------------------------------------#
00757         # Show spectrum data at mouse position
00758         def spec_data(event):
00759             # Getting spectrum data of neiboring point
00760             xclick = event.xdata
00761             if event.inaxes != panel0:
00762                 return
00763             ipoint = len(xdata)-1
00764             for i in range(len(xdata)-1):
00765                 xl = xclick-xdata[i]
00766                 xr = xclick-xdata[i+1]
00767                 if xl*xr <= 0.:
00768                     ipoint = i
00769                     break
00770             # Output spectral value on the navigation window
00771             posi = '[ %s, %s ]:  x = %.2f   value = %.2f'\
00772                   %(titl,titp,xdata[ipoint],ydata[ipoint])
00773             #naviwin.posi.set(posi)
00774             thetoolbar.set_message(posi)
00775         #------------------------------------------------------#
00776         # Disconnect from mouse events
00777         def discon(event):
00778             #naviwin.window.destroy()
00779             theplot.register('motion_notify',None)
00780             # Re-activate the default motion_notify_event
00781             thetoolbar._idDrag=thecanvas.mpl_connect('motion_notify_event',
00782                                                      thetoolbar.mouse_move)
00783             theplot.register('button_release',None)
00784             return
00785         #------------------------------------------------------#
00786         # Show data value along with mouse movement
00787         theplot.register('motion_notify',spec_data)
00788         # Finish events when mouse button is released
00789         theplot.register('button_release',discon)
00790 
00791 
00792     ### Notation
00793     def _mod_note(self,event):
00794         # Do not fire event when in zooming/panning mode
00795         if not self.figmgr.toolbar.mode == '':
00796             return
00797         if event.button == 1:
00798             self.notewin.load_textwindow(event)
00799         elif event.button == 3 and self._note_picked(event):
00800             self.notewin.load_modmenu(event)
00801         return
00802 
00803     def _note_picked(self,event):
00804         # just briefly check if any texts are picked
00805         for textobj in self.canvas.figure.texts:
00806             if textobj.contains(event)[0]:
00807                 return True
00808         for ax in self.canvas.figure.axes:
00809             for textobj in ax.texts:
00810                 if textobj.contains(event)[0]:
00811                     return True
00812         return False
00813 
00814     ### Region/Panel selection & oparations
00815     ### add regions to selections
00816     @asaplog_post_dec
00817     def _add_region(self,event):
00818         if not self.figmgr.toolbar.mode == '':
00819             return
00820         if event.button != 1 or event.inaxes == None:
00821             return
00822         # this row resolution assumes row panelling
00823         irow = int(self._getrownum(event.inaxes))
00824         if irow in self._selpanels:
00825             msg = "The whole spectrum is already selected"
00826             asaplog.post()
00827             asaplog.push(msg)
00828             asaplog.post('WARN')
00829             return
00830         self._thisregion = {'axes': event.inaxes,'xs': event.x,
00831                             'worldx': [event.xdata,event.xdata]}
00832         self.plotter._plotter.register('button_press',None)
00833         self.xold = event.x
00834         self.xdataold = event.xdata
00835         self.plotter._plotter.register('motion_notify', self._xspan_draw)
00836         self.plotter._plotter.register('button_press', self._xspan_end)
00837 
00838     def _xspan_draw(self,event):
00839         if event.inaxes == self._thisregion['axes']:
00840             xnow = event.x
00841             self.xold = xnow
00842             self.xdataold = event.xdata
00843         else:
00844             xnow = self.xold
00845         try: self.lastspan
00846         except AttributeError: pass
00847         else:
00848             if self.lastspan: self._remove_span(self.lastspan)
00849 
00850         #self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="#555555",stipple="gray50")
00851         self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="")
00852         del xnow
00853 
00854     def _draw_span(self,axes,x0,x1,**kwargs):
00855         pass
00856 
00857     def _remove_span(self,span):
00858         pass
00859 
00860     @asaplog_post_dec
00861     def _xspan_end(self,event):
00862         if not self.figmgr.toolbar.mode == '':
00863             return
00864         if event.button != 1:
00865             return
00866 
00867         try: self.lastspan
00868         except AttributeError: pass
00869         else:
00870             self._remove_span(self.lastspan)
00871             del self.lastspan
00872         if event.inaxes == self._thisregion['axes']:
00873             xdataend = event.xdata
00874         else:
00875             xdataend = self.xdataold
00876 
00877         self._thisregion['worldx'][1] = xdataend
00878         lregion = self._thisregion['worldx']
00879         # WORKAROUND for the issue axvspan started to reset xlim.
00880         axlimx = self._thisregion['axes'].get_xlim()
00881         pregion = self._thisregion['axes'].axvspan(lregion[0],lregion[1],
00882                                                    facecolor='0.7')
00883         self._thisregion['axes'].set_xlim(axlimx)
00884         
00885         self.plotter._plotter.canvas.draw()
00886         self._polygons.append(pregion)
00887         srow = self._getrownum(self._thisregion['axes'])
00888         irow = int(srow)
00889         if not self._selregions.has_key(srow):
00890             self._selregions[srow] = []
00891         self._selregions[srow].append(lregion)
00892         del lregion, pregion, xdataend
00893         sout = "selected region: "+str(self._thisregion['worldx'])+\
00894               "(@row "+str(self._getrownum(self._thisregion['axes']))+")"
00895         asaplog.push(sout)
00896 
00897         # release event
00898         self.plotter._plotter.register('button_press',None)
00899         self.plotter._plotter.register('motion_notify',None)
00900         # Clear up region selection
00901         self._thisregion = None
00902         self.xdataold = None
00903         self.xold = None
00904         # finally recover region selection event
00905         self.plotter._plotter.register('button_press',self._add_region)
00906 
00907     ### add panels to selections
00908     @asaplog_post_dec
00909     def _add_panel(self,event):
00910         if not self.figmgr.toolbar.mode == '':
00911             return
00912         if event.button != 1 or event.inaxes == None:
00913             return
00914         selax = event.inaxes
00915         # this row resolution assumes row panelling
00916         srow = self._getrownum(selax)
00917         irow = int(srow)
00918         if srow:
00919             self._selpanels.append(irow)
00920         shadow = Rectangle((0,0),1,1,facecolor='0.7',transform=selax.transAxes,visible=True)
00921         self._polygons.append(selax.add_patch(shadow))
00922         #self.plotter._plotter.show(False)
00923         self.plotter._plotter.canvas.draw()
00924         asaplog.push("row "+str(irow)+" is selected")
00925         ## check for region selection of the spectra and overwrite it.
00926         ##!!!! currently disabled for consistency with flag tools !!!!
00927         #if self._selregions.has_key(srow):
00928         #    self._selregions.pop(srow)
00929         #    msg = "The whole spectrum is selected for row="+srow+". Region selection will be overwritten."
00930         #    asaplog.push(msg)
00931 
00932     def _getrownum(self,axis):
00933         ### returns the row number of selected spectrum as a string ###
00934         plabel = axis.get_title()
00935         if plabel.startswith("row "):
00936             return plabel.strip("row ")
00937         return None
00938 
00939     def _any_selection(self):
00940         ### returns if users have selected any spectrum or region ###
00941         if len(self._selpanels) or len(self._selregions):
00942             return True
00943         return False
00944 
00945     def _plot_selections(self,regions=None,panels=None):
00946         ### mark panels/spectra selections in the page
00947         if not self._any_selection() and not (regions or panels):
00948             return
00949         regions = regions or self._selregions.copy() or {}
00950         panels = panels or self._selpanels or []
00951         if not isinstance(regions,dict):
00952             asaplog.post()
00953             asaplog.push("Invalid region specification")
00954             asaplog.post('ERROR')
00955         if not isinstance(panels,list):
00956             asaplog.post()
00957             asaplog.push("Invalid panel specification")
00958             asaplog.post('ERROR')
00959         strow = self._getrownum(self.plotter._plotter.subplots[0]['axes'])
00960         enrow = self._getrownum(self.plotter._plotter.subplots[-1]['axes'])
00961         for irow in range(int(strow),int(enrow)+1):
00962             if regions.has_key(str(irow)):
00963                 ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
00964                 mlist = regions.pop(str(irow))
00965                 # WORKAROUND for the issue axvspan started to reset xlim.
00966                 axlimx = ax.get_xlim()
00967                 for i in range(len(mlist)):
00968                     self._polygons.append(ax.axvspan(mlist[i][0],mlist[i][1],
00969                                                      facecolor='0.7'))
00970                 ax.set_xlim(axlimx)
00971                 del ax,mlist,axlimx
00972             if irow in panels:
00973                 ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
00974                 shadow = Rectangle((0,0),1,1,facecolor='0.7',
00975                                    transform=ax.transAxes,visible=True)
00976                 self._polygons.append(ax.add_patch(shadow))
00977                 del ax,shadow
00978         self.plotter._plotter.canvas.draw()
00979         del regions,panels,strow,enrow
00980 
00981     def _clear_selection_plot(self, refresh=True):
00982         ### clear up polygons which mark selected spectra and regions ###
00983         if len(self._polygons) > 0:
00984             for shadow in self._polygons:
00985                 shadow.remove()
00986             if refresh: self.plotter._plotter.canvas.draw()
00987         self._polygons = []
00988 
00989     def _clearup_selections(self, refresh=True):
00990         # clear-up selection and polygons
00991         self._selpanels = []
00992         self._selregions = {}
00993         self._clear_selection_plot(refresh=refresh)
00994 
00995     ### clear up selections
00996     def cancel_select(self):
00997         self.figmgr.toolbar.set_message('selections canceled')
00998         # clear-up selection and polygons
00999         self._clearup_selections(refresh=True)
01000 
01001     ### flag selected spectra/regions
01002     @asaplog_post_dec
01003     def flag(self):
01004         if not self._any_selection():
01005             msg = "No selection to be Flagged"
01006             asaplog.post()
01007             asaplog.push(msg)
01008             asaplog.post('WARN')
01009             return
01010         self._pause_buttons(operation="start",msg="Flagging data...")
01011         self._flag_operation(rows=self._selpanels,
01012                              regions=self._selregions,unflag=False)
01013         sout = "Flagged:\n"
01014         sout += "  rows = "+str(self._selpanels)+"\n"
01015         sout += "  regions: "+str(self._selregions)
01016         asaplog.push(sout)
01017         del sout
01018         self.plotter._ismodified = True
01019         self._clearup_selections(refresh=False)
01020         self._plot_page(pagemode="current")
01021         self._pause_buttons(operation="end")
01022 
01023     ### unflag selected spectra/regions
01024     @asaplog_post_dec
01025     def unflag(self):
01026         if not self._any_selection():
01027             msg = "No selection to be Flagged"
01028             asaplog.push(msg)
01029             asaplog.post('WARN')
01030             return
01031         self._pause_buttons(operation="start",msg="Unflagging data...")
01032         self._flag_operation(rows=self._selpanels,
01033                              regions=self._selregions,unflag=True)
01034         sout = "Unflagged:\n"
01035         sout += "  rows = "+str(self._selpanels)+"\n"
01036         sout += "  regions: "+str(self._selregions)
01037         asaplog.push(sout)
01038         del sout
01039         self.plotter._ismodified = True
01040         self._clearup_selections(refresh=False)
01041         self._plot_page(pagemode="current")
01042         self._pause_buttons(operation="end")
01043 
01044     ### actual flag operation
01045     @asaplog_post_dec
01046     def _flag_operation(self,rows=None,regions=None,unflag=False):
01047         scan = self.plotter._data
01048         if not scan:
01049             asaplog.post()
01050             asaplog.push("Invalid scantable")
01051             asaplog.post("ERROR")
01052         if isinstance(rows,list) and len(rows) > 0:
01053             scan.flag_row(rows=rows,unflag=unflag)
01054         if isinstance(regions,dict) and len(regions) > 0:
01055             for srow, masklist in regions.iteritems():
01056                 if not isinstance(masklist,list) or len(masklist) ==0:
01057                     msg = "Ignoring invalid region selection for row = "+srow
01058                     asaplog.post()
01059                     asaplog.push(msg)
01060                     asaplog.post("WARN")
01061                     continue
01062                 irow = int(srow)
01063                 mask = scan.create_mask(masklist,invert=False,row=irow)
01064                 scan.flag(row=irow,mask=mask,unflag=unflag)
01065                 del irow, mask
01066             del srow, masklist
01067         del scan
01068 
01069     ### show statistics of selected spectra/regions
01070     @asaplog_post_dec
01071     def stat_cal(self):
01072         if not self._any_selection():
01073             msg = "No selection to be calculated"
01074             asaplog.push(msg)
01075             asaplog.post('WARN')
01076             return
01077         self._selected_stats(rows=self._selpanels,regions=self._selregions)
01078         self._clearup_selections(refresh=True)
01079 
01080     @asaplog_post_dec
01081     def _selected_stats(self,rows=None,regions=None):
01082         scan = self.plotter._data
01083         if not scan:
01084             asaplog.post()
01085             asaplog.push("Invalid scantable")
01086             asaplog.post("ERROR")
01087         mathobj = stmath( rcParams['insitu'] )
01088         statval = {}
01089         statstr = ['max', 'min', 'mean', 'median', 'sum', 'stddev', 'rms']
01090         if isinstance(rows, list) and len(rows) > 0:
01091             for irow in rows:
01092                 for stat in statstr:
01093                     statval[stat] = mathobj._statsrow(scan,[],stat,irow)[0]
01094                 self._print_stats(scan,irow,statval,statstr=statstr)
01095             del irow
01096         if isinstance(regions,dict) and len(regions) > 0:
01097             for srow, masklist in regions.iteritems():
01098                 if not isinstance(masklist,list) or len(masklist) ==0:
01099                     msg = "Ignoring invalid region selection for row = "+srow
01100                     asaplog.post()
01101                     asaplog.push(msg)
01102                     asaplog.post("WARN")
01103                     continue
01104                 irow = int(srow)
01105                 mask = scan.create_mask(masklist,invert=False,row=irow)
01106                 for stat in statstr:
01107                     statval[stat] = mathobj._statsrow(scan,mask,stat,irow)[0]
01108                 self._print_stats(scan,irow,statval,statstr=statstr,
01109                                   mask=masklist)
01110                 del irow, mask
01111             del srow, masklist
01112         del scan, statval, mathobj
01113 
01114     @asaplog_post_dec
01115     def _print_stats(self,scan,row,stats,statstr=None,mask=None):
01116         if not isinstance(scan, scantable):
01117             asaplog.post()
01118             asaplog.push("Invalid scantable")
01119             asaplog.post("ERROR")
01120         if row < 0 or row > scan.nrow():
01121             asaplog.post()
01122             asaplog.push("Invalid row number")
01123             asaplog.post("ERROR")
01124         if not isinstance(stats,dict) or len(stats) == 0:
01125             asaplog.post()
01126             asaplog.push("Invalid statistic value")
01127             asaplog.post("ERROR")
01128         maskstr = "All"
01129         if mask:
01130             maskstr = str(mask)
01131         ssep = "-"*70+"\n"
01132         sout = ssep
01133         sout += ("Row=%d  Scan=%d  IF=%d  Pol=%d  Time=%s  mask=%s" % \
01134                  (row, scan.getscan(row), scan.getif(row), scan.getpol(row), scan.get_time(row),maskstr))
01135         sout += "\n"
01136         statvals = []
01137         if not len(statstr):
01138             statstr = stats.keys()
01139         for key in statstr:
01140             sout += key.ljust(10)
01141             statvals.append(stats.pop(key))
01142         sout += "\n"
01143         sout += ("%f "*len(statstr) % tuple(statvals))
01144         sout += "\n"+ssep
01145         asaplog.push(sout)
01146         del sout, ssep, maskstr, statvals, key, scan, row, stats, statstr, mask
01147 
01148     ### Page chages
01149     ### go to the previous page
01150     def prev_page(self):
01151         self._pause_buttons(operation="start",msg='plotting the previous page')
01152         self._clear_selection_plot(refresh=False)
01153         self._plot_page(pagemode="prev")
01154         self._plot_selections()
01155         self._pause_buttons(operation="end")
01156 
01157     ### go to the next page
01158     def next_page(self):
01159         self._pause_buttons(operation="start",msg='plotting the next page')
01160         self._clear_selection_plot(refresh=False)
01161         self._plot_page(pagemode="next")
01162         self._plot_selections()
01163         self._pause_buttons(operation="end")
01164 
01165     ### actual plotting of the new page
01166     def _plot_page(self,pagemode="next"):
01167         if self.plotter._startrow <= 0:
01168             msg = "The page counter is reset due to chages of plot settings. "
01169             msg += "Plotting from the first page."
01170             asaplog.post()
01171             asaplog.push(msg)
01172             asaplog.post('WARN')
01173             goback = False
01174 
01175         self.plotter._plotter.hold()
01176         #self.plotter._plotter.legend(1)
01177         self._set_plot_counter(pagemode)
01178         self.plotter._plot(self.plotter._data)
01179         self.set_pagecounter(self._get_pagenum())
01180         self.plotter._plotter.release()
01181         self.plotter._plotter.tidy()
01182         self.plotter._plotter.show(hardrefresh=False)
01183 
01184     ### calculate the panel ID and start row to plot a page
01185     #def _set_prevpage_counter(self):
01186     def _set_plot_counter(self, pagemode):
01187         ## page operation should be either "previous", "current", or "next"
01188         availpage = ["p","c","n"]
01189         pageop = pagemode[0].lower()
01190         if not (pageop in availpage):
01191             asaplog.post()
01192             asaplog.push("Invalid page operation")
01193             asaplog.post("ERROR")
01194         if pageop == "n":
01195             # nothing necessary to plot the next page
01196             return
01197         # set row and panel counters to those of the 1st panel of previous page
01198         maxpanel = 25
01199         # the ID of the last panel in current plot
01200         lastpanel = self.plotter._ipanel
01201         # the number of current subplots
01202         currpnum = len(self.plotter._plotter.subplots)
01203 
01204         # the nuber of previous subplots
01205         start_ipanel = None
01206         if pageop == "c":
01207             start_ipanel = max(lastpanel-currpnum+1, 0)
01208         else:
01209             ## previous page
01210             prevpnum = None
01211             if self.plotter._rows and self.plotter._cols:
01212                 # when user set layout
01213                 prevpnum = self.plotter._rows*self.plotter._cols
01214             else:
01215                 # no user specification
01216                 prevpnum = maxpanel
01217             start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
01218             del prevpnum
01219 
01220         # set the pannel ID of the last panel of the prev(-prev) page
01221         self.plotter._ipanel = start_ipanel-1
01222         if self.plotter._panelling == 'r':
01223             self.plotter._startrow = start_ipanel
01224         else:
01225             # the start row number of the next panel
01226             self.plotter._startrow = self.plotter._panelrows[start_ipanel]
01227         del lastpanel,currpnum,start_ipanel
01228 
01229     ### refresh the page counter
01230     def set_pagecounter(self,page):
01231         nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
01232         nwidth = max(nwidth,4)
01233         formatstr = '%'+str(nwidth)+'d'
01234         self.show_pagenum(page,formatstr)
01235 
01236     def show_pagenum(self,pagenum,formatstr):
01237         # passed to backend dependent class
01238         pass
01239 
01240     def _get_pagenum(self):
01241         maxpanel = 25
01242         # get the ID of last panel in the current page
01243         idlastpanel = self.plotter._ipanel
01244         if self.plotter._rows and self.plotter._cols:
01245             ppp = self.plotter._rows*self.plotter._cols
01246         else:
01247             ppp = maxpanel
01248         return int(idlastpanel/ppp)+1
01249 
01250     # pause buttons for slow operations. implemented at a backend dependent class
01251     def _pause_buttons(self,operation="end",msg=""):
01252         pass