source: trunk/python/customgui_base.py@ 2766

Last change on this file since 2766 was 2697, checked in by Kana Sugimoto, 12 years ago

New Development: No

JIRA Issue: No

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs: iterate pages

Put in Release Notes: No

Module(s): asapplotter and sdplot

Description: An attempt to speed-up page iterations in ASAP plotter.


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