source: tags/asap-4.0.0/python/customgui_base.py@ 2354

Last change on this file since 2354 was 2324, checked in by Malte Marquarding, 13 years ago

Fixed bug in plotetr based statistics where median always showed nan. Need to get stats function explictly from the numpy.ma module. Also put in a filter for the stats to not show stats for annotations. Everything with a label starting with '_' gets ignored.

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