source: trunk/python/customgui_base.py@ 2235

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

New Development: No

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

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

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

Put in Release Notes: No

Module(s): asapplotter, sdplot

Description:

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


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