source: trunk/python/customgui_base.py@ 2798

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

New Development: No

JIRA Issue: Yes (CAS-4859)

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs:

set asap.rcParams[scantable.storage] = 'disk'
run asap.plotter.plot
exit from casapy or asap. A temporary scantable on disk should be deleted.

Put in Release Notes: No

Module(s): asap.plotter, sdplot

Description:

There were cyclic references between plotters and toolbars, i.e., plotter and CustomToolbarCommon,
and flagplotter and CustomFlagToolbarCommon, which prevented deletion of temporary scantables
from disk when exitting sessions.
I changed the reference to plotters in toolbars as a weak reference so that cyclic referencing
should not avoid desctruction of the plotters. It should be ok because there shouldn't be a case
where toolbar exists without plotter, but I'm not 100% sure to be honest.


File size: 46.6 KB
Line 
1import os
2import weakref
3import matplotlib, numpy
4from asap.logging import asaplog, asaplog_post_dec
5from matplotlib.patches import Rectangle
6from asap.parameters import rcParams
7from asap import scantable
8from asap._asap import stmath
9from asap.utils import _n_bools, mask_not, mask_or
10
11######################################
12## Add CASA custom toolbar ##
13######################################
14class CustomToolbarCommon:
15 def __init__(self,parent):
16 self.plotter = weakref.ref(parent)
17 #self.figmgr=self.plotter._plotter.figmgr
18
19 ### select the nearest spectrum in pick radius
20 ### and display spectral value on the toolbar.
21 def _select_spectrum(self,event):
22 # Do not fire event when in zooming/panning mode
23 mode = self.figmgr.toolbar.mode
24 if not mode == '':
25 return
26 # When selected point is out of panels
27 if event.inaxes == None:
28 return
29 # If not left button
30 if event.button != 1:
31 return
32
33 xclick = event.xdata
34 yclick = event.ydata
35 dist2 = 1000.
36 pickline = None
37 # If the pannel has picable objects
38 pflag = False
39 for lin in event.inaxes.lines:
40 if not lin.pickable():
41 continue
42 pflag = True
43 flag,pind = lin.contains(event)
44 if not flag:
45 continue
46 # Get nearest point
47 inds = pind['ind']
48 xlin = lin.get_xdata()
49 ylin = lin.get_ydata()
50 for i in inds:
51 d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
52 if dist2 >= d2:
53 dist2 = d2
54 pickline = lin
55 # No pickcable line in the pannel
56 if not pflag:
57 return
58 # Pickable but too far from mouse position
59 elif pickline is None:
60 picked = 'No line selected.'
61 self.figmgr.toolbar.set_message(picked)
62 return
63 del pind, inds, xlin, ylin
64 # Spectra are Picked
65 theplot = self.plotter._plotter
66 thetoolbar = self.figmgr.toolbar
67 thecanvas = self.figmgr.canvas
68 # Disconnect the default motion notify event
69 # Notice! the other buttons are also diabled!!!
70 thecanvas.mpl_disconnect(thetoolbar._idDrag)
71 # Get picked spectrum
72 xdata = pickline.get_xdata()
73 ydata = pickline.get_ydata()
74 titl = pickline.get_label()
75 titp = event.inaxes.title.get_text()
76 panel0 = event.inaxes
77 picked = "Selected: '"+titl+"' in panel '"+titp+"'."
78 thetoolbar.set_message(picked)
79 # Generate a navigation window
80 #naviwin=Navigationwindow(titp,titl)
81 #------------------------------------------------------#
82 # Show spectrum data at mouse position
83 def spec_data(event):
84 # Getting spectrum data of neiboring point
85 xclick = event.xdata
86 if event.inaxes != panel0:
87 return
88 ipoint = len(xdata)-1
89 for i in range(len(xdata)-1):
90 xl = xclick - xdata[i]
91 xr = xclick - xdata[i+1]
92 if xl*xr <= 0.:
93 ipoint = i
94 break
95 # Output spectral value on the navigation window
96 posi = '[ %s, %s ]: x = %.2f value = %.2f'\
97 %(titl,titp,xdata[ipoint],ydata[ipoint])
98 #naviwin.posi.set(posi)
99 thetoolbar.set_message(posi)
100 #------------------------------------------------------#
101 # Disconnect from mouse events
102 def discon(event):
103 #naviwin.window.destroy()
104 theplot.register('motion_notify',None)
105 # Re-activate the default motion_notify_event
106 thetoolbar._idDrag = thecanvas.mpl_connect('motion_notify_event',
107 thetoolbar.mouse_move)
108 theplot.register('button_release',None)
109 return
110 #------------------------------------------------------#
111 # Show data value along with mouse movement
112 theplot.register('motion_notify',spec_data)
113 # Finish events when mouse button is released
114 theplot.register('button_release',discon)
115
116
117 ### Notation
118 def _mod_note(self,event):
119 # Do not fire event when in zooming/panning mode
120 if not self.figmgr.toolbar.mode == '':
121 return
122 if event.button == 1:
123 self.notewin.load_textwindow(event)
124 elif event.button == 3 and self._note_picked(event):
125 self.notewin.load_modmenu(event)
126 return
127
128 def _note_picked(self,event):
129 # just briefly check if any texts are picked
130 for textobj in self.canvas.figure.texts:
131 if textobj.contains(event)[0]:
132 return True
133 for ax in self.canvas.figure.axes:
134 for textobj in ax.texts:
135 if textobj.contains(event)[0]:
136 return True
137 #print "No text picked"
138 return False
139
140
141 ### Purely plotter based statistics calculation of a selected area.
142 ### No access to scantable
143 def _single_mask(self,event):
144 # Do not fire event when in zooming/panning mode
145 if not self.figmgr.toolbar.mode == '':
146 return
147 # When selected point is out of panels
148 if event.inaxes == None:
149 return
150 if event.button == 1:
151 exclude=False
152 elif event.button == 3:
153 exclude=True
154 else:
155 return
156
157 self._thisregion = {'axes': event.inaxes,'xs': event.x,
158 'worldx': [event.xdata,event.xdata],
159 'invert': exclude}
160 self.xold = event.x
161 self.xdataold = event.xdata
162
163 self.plotter._plotter.register('button_press',None)
164 self.plotter._plotter.register('motion_notify', self._xspan_draw)
165 self.plotter._plotter.register('button_press', self._xspan_end)
166
167 def _xspan_draw(self,event):
168 if event.inaxes == self._thisregion['axes']:
169 xnow = event.x
170 self.xold = xnow
171 self.xdataold = event.xdata
172 else:
173 xnow = self.xold
174 try: self.lastspan
175 except AttributeError: pass
176 else:
177 if self.lastspan: self._remove_span(self.lastspan)
178
179 self.lastspan = self._draw_span(self._thisregion['axes'],
180 self._thisregion['xs'], xnow, fill="")
181 del xnow
182
183 def _draw_span(self,axes,x0,x1,**kwargs):
184 pass
185
186 def _remove_span(self,span):
187 pass
188
189 @asaplog_post_dec
190 def _xspan_end(self,event):
191 if not self.figmgr.toolbar.mode == '':
192 return
193 #if event.button != 1:
194 # return
195
196 try: self.lastspan
197 except AttributeError: pass
198 else:
199 self._remove_span(self.lastspan)
200 del self.lastspan
201 if event.inaxes == self._thisregion['axes']:
202 xdataend = event.xdata
203 else:
204 xdataend = self.xdataold
205
206 self._thisregion['worldx'][1] = xdataend
207 # print statistics of spectra in subplot
208 self._subplot_stats(self._thisregion)
209
210 # release event
211 self.plotter._plotter.register('button_press',None)
212 self.plotter._plotter.register('motion_notify',None)
213 # Clear up region selection
214 self._thisregion = None
215 self.xdataold = None
216 self.xold = None
217 # finally recover region selection event
218 self.plotter._plotter.register('button_press',self._single_mask)
219
220 def _subplot_stats(self,selection):
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 # Don't include annotations
228 if line.get_label().startswith("_"):
229 continue
230 label = panelstr + ", "+line.get_label()
231 x = line.get_xdata()
232 newmsk = None
233 selmsk = self._create_flag_from_array(x,
234 selection['worldx'],
235 selection['invert'])
236 ydat = None
237 y = line.get_ydata()
238 if numpy.ma.isMaskedArray(y):
239 ydat = y.data
240 basemsk = y.mask
241 else:
242 ydat = y
243 basemsk = False
244 if not isinstance(basemsk, bool):
245 # should be ndarray
246 newmsk = mask_or(selmsk, basemsk)
247 elif basemsk:
248 # the whole original spectrum is flagged
249 newmsk = basemsk
250 else:
251 # no channel was flagged originally
252 newmsk = selmsk
253 mdata = numpy.ma.masked_array(ydat, mask=newmsk)
254 statval = {}
255 for stat in statstr:
256 # need to get the stat functions from the ma module!!!
257 statval[stat] = getattr(numpy.ma,stat)(mdata)
258 self._print_stats(statval, statstr=statstr, label=label,\
259 mask=selection['worldx'],\
260 unmask=selection['invert'])
261 asaplog.push(ssep)
262 asaplog.post()
263
264 def _create_flag_from_array(self,x,masklist,invert):
265 # Return True for channels which should be EXCLUDED (flag)
266 if len(masklist) <= 1:
267 asaplog.push()
268 asaplog.post("masklist should be a list of 2 elements")
269 asaplog.push("ERROR")
270 n = len(x)
271 # Base mask: flag out all channels
272 mask = _n_bools(n, True)
273 minval = min(masklist[0:2])
274 maxval = max(masklist[0:2])
275 for i in range(n):
276 if minval <= x[i] <= maxval:
277 mask[i] = False
278 if invert:
279 mask = mask_not(mask)
280 return mask
281
282 @asaplog_post_dec
283 def _print_stats(self,stats,statstr=None,label="",mask=None,unmask=False):
284 if not isinstance(stats,dict) or len(stats) == 0:
285 asaplog.post()
286 asaplog.push("Invalid statistic value")
287 asaplog.post("ERROR")
288 maskstr = "Not available"
289 if mask:
290 masktype = "mask"
291 maskstr = str(mask)
292 if unmask: masktype = "unmask"
293
294 sout = label + ", " + masktype + " = " + maskstr + "\n"
295 statvals = []
296 if not len(statstr):
297 statstr = stats.keys()
298 for key in statstr:
299 sout += key.ljust(10)
300 statvals.append(stats.pop(key))
301 sout += "\n"
302 sout += ("%f "*len(statstr) % tuple(statvals))
303 asaplog.push(sout)
304 #del sout, maskstr, masktype, statvals, key, stats, statstr, mask, label
305
306
307 ### Page chages
308 ### go to the previous page
309 def prev_page(self):
310 self.figmgr.toolbar.set_message('plotting the previous page')
311 #self._pause_buttons(operation="start",msg='plotting the previous page')
312 self._new_page(goback=True)
313 #self._pause_buttons(operation="end")
314
315 ### go to the next page
316 def next_page(self):
317 self.figmgr.toolbar.set_message('plotting the next page')
318 #self._pause_buttons(operation="start",msg='plotting the next page')
319 self._new_page(goback=False)
320 #self._pause_buttons(operation="end")
321
322 ### actual plotting of the new page
323 def _new_page(self,goback=False):
324 top = None
325 header = self.plotter._headtext
326 reset = False
327 doheader = (isinstance(header['textobj'],list) and \
328 len(header['textobj']) > 0)
329 if doheader:
330 top = self.plotter._plotter.figure.subplotpars.top
331 fontsize = header['textobj'][0].get_fontproperties().get_size()
332 if self.plotter._startrow <= 0:
333 msg = "The page counter is reset due to chages of plot settings. "
334 msg += "Plotting from the first page."
335 asaplog.push(msg)
336 asaplog.post('WARN')
337 reset = True
338 goback = False
339 if doheader:
340 extrastr = selstr = ''
341 if header.has_key('extrastr'):
342 extrastr = header['extrastr']
343 if header.has_key('selstr'):
344 selstr = header['selstr']
345 self.plotter._reset_header()
346
347 self.plotter._plotter.hold()
348 if goback:
349 self._set_prevpage_counter()
350 #self.plotter._plotter.clear()
351 self.plotter._plot(self.plotter._data)
352 pagenum = self._get_pagenum()
353 self.set_pagecounter(pagenum)
354 # Plot header information
355 #if header['textobj']:
356 if doheader and pagenum == 1:
357 if top and top != self.plotter._margins[3]:
358 # work around for sdplot in CASA. complete checking in future?
359 self.plotter._plotter.figure.subplots_adjust(top=top)
360 if reset:
361 self.plotter.print_header(plot=True,fontsize=fontsize,selstr=selstr, extrastr=extrastr)
362 else:
363 self.plotter._header_plot(header['string'],fontsize=fontsize)
364 self.plotter._plotter.release()
365 self.plotter._plotter.tidy()
366 self.plotter._plotter.show(hardrefresh=False)
367 del top
368
369 ### calculate the panel ID and start row to plot the previous page
370 def _set_prevpage_counter(self):
371 # set row and panel counters to those of the 1st panel of previous page
372 maxpanel = 16
373 # the ID of the last panel in current plot
374 lastpanel = self.plotter._ipanel
375 # the number of current subplots
376 currpnum = len(self.plotter._plotter.subplots)
377 # the nuber of previous subplots
378 prevpnum = None
379 if self.plotter._rows and self.plotter._cols:
380 # when user set layout
381 prevpnum = self.plotter._rows*self.plotter._cols
382 else:
383 # no user specification
384 prevpnum = maxpanel
385
386 start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
387 # set the pannel ID of the last panel of prev-prev page
388 self.plotter._ipanel = start_ipanel-1
389 if self.plotter._panelling == 'r':
390 self.plotter._startrow = start_ipanel
391 else:
392 # the start row number of the next panel
393 self.plotter._startrow = self.plotter._panelrows[start_ipanel]
394 del lastpanel,currpnum,prevpnum,start_ipanel
395
396 ### refresh the page counter
397 def set_pagecounter(self,page):
398 nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
399 nwidth = max(nwidth,4)
400 formatstr = '%'+str(nwidth)+'d'
401 self.show_pagenum(page,formatstr)
402
403 def show_pagenum(self,pagenum,formatstr):
404 # passed to backend dependent class
405 pass
406
407 def _get_pagenum(self):
408 # get the ID of last panel in the current page
409 idlastpanel = self.plotter._ipanel
410 # max panels in a page
411 ppp = self.plotter._plotter.rows*self.plotter._plotter.cols
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=weakref.ref(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 # WORKAROUND for the issue axvspan started to reset xlim.
880 axlimx = self._thisregion['axes'].get_xlim()
881 pregion = self._thisregion['axes'].axvspan(lregion[0],lregion[1],
882 facecolor='0.7')
883 self._thisregion['axes'].set_xlim(axlimx)
884
885 self.plotter._plotter.canvas.draw()
886 self._polygons.append(pregion)
887 srow = self._getrownum(self._thisregion['axes'])
888 irow = int(srow)
889 if not self._selregions.has_key(srow):
890 self._selregions[srow] = []
891 self._selregions[srow].append(lregion)
892 del lregion, pregion, xdataend
893 sout = "selected region: "+str(self._thisregion['worldx'])+\
894 "(@row "+str(self._getrownum(self._thisregion['axes']))+")"
895 asaplog.push(sout)
896
897 # release event
898 self.plotter._plotter.register('button_press',None)
899 self.plotter._plotter.register('motion_notify',None)
900 # Clear up region selection
901 self._thisregion = None
902 self.xdataold = None
903 self.xold = None
904 # finally recover region selection event
905 self.plotter._plotter.register('button_press',self._add_region)
906
907 ### add panels to selections
908 @asaplog_post_dec
909 def _add_panel(self,event):
910 if not self.figmgr.toolbar.mode == '':
911 return
912 if event.button != 1 or event.inaxes == None:
913 return
914 selax = event.inaxes
915 # this row resolution assumes row panelling
916 srow = self._getrownum(selax)
917 irow = int(srow)
918 if srow:
919 self._selpanels.append(irow)
920 shadow = Rectangle((0,0),1,1,facecolor='0.7',transform=selax.transAxes,visible=True)
921 self._polygons.append(selax.add_patch(shadow))
922 #self.plotter._plotter.show(False)
923 self.plotter._plotter.canvas.draw()
924 asaplog.push("row "+str(irow)+" is selected")
925 ## check for region selection of the spectra and overwrite it.
926 ##!!!! currently disabled for consistency with flag tools !!!!
927 #if self._selregions.has_key(srow):
928 # self._selregions.pop(srow)
929 # msg = "The whole spectrum is selected for row="+srow+". Region selection will be overwritten."
930 # asaplog.push(msg)
931
932 def _getrownum(self,axis):
933 ### returns the row number of selected spectrum as a string ###
934 plabel = axis.get_title()
935 if plabel.startswith("row "):
936 return plabel.strip("row ")
937 return None
938
939 def _any_selection(self):
940 ### returns if users have selected any spectrum or region ###
941 if len(self._selpanels) or len(self._selregions):
942 return True
943 return False
944
945 def _plot_selections(self,regions=None,panels=None):
946 ### mark panels/spectra selections in the page
947 if not self._any_selection() and not (regions or panels):
948 return
949 regions = regions or self._selregions.copy() or {}
950 panels = panels or self._selpanels or []
951 if not isinstance(regions,dict):
952 asaplog.post()
953 asaplog.push("Invalid region specification")
954 asaplog.post('ERROR')
955 if not isinstance(panels,list):
956 asaplog.post()
957 asaplog.push("Invalid panel specification")
958 asaplog.post('ERROR')
959 strow = self._getrownum(self.plotter._plotter.subplots[0]['axes'])
960 enrow = self._getrownum(self.plotter._plotter.subplots[-1]['axes'])
961 for irow in range(int(strow),int(enrow)+1):
962 if regions.has_key(str(irow)):
963 ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
964 mlist = regions.pop(str(irow))
965 # WORKAROUND for the issue axvspan started to reset xlim.
966 axlimx = ax.get_xlim()
967 for i in range(len(mlist)):
968 self._polygons.append(ax.axvspan(mlist[i][0],mlist[i][1],
969 facecolor='0.7'))
970 ax.set_xlim(axlimx)
971 del ax,mlist,axlimx
972 if irow in panels:
973 ax = self.plotter._plotter.subplots[irow - int(strow)]['axes']
974 shadow = Rectangle((0,0),1,1,facecolor='0.7',
975 transform=ax.transAxes,visible=True)
976 self._polygons.append(ax.add_patch(shadow))
977 del ax,shadow
978 self.plotter._plotter.canvas.draw()
979 del regions,panels,strow,enrow
980
981 def _clear_selection_plot(self, refresh=True):
982 ### clear up polygons which mark selected spectra and regions ###
983 if len(self._polygons) > 0:
984 for shadow in self._polygons:
985 shadow.remove()
986 if refresh: self.plotter._plotter.canvas.draw()
987 self._polygons = []
988
989 def _clearup_selections(self, refresh=True):
990 # clear-up selection and polygons
991 self._selpanels = []
992 self._selregions = {}
993 self._clear_selection_plot(refresh=refresh)
994
995 ### clear up selections
996 def cancel_select(self):
997 self.figmgr.toolbar.set_message('selections canceled')
998 # clear-up selection and polygons
999 self._clearup_selections(refresh=True)
1000
1001 ### flag selected spectra/regions
1002 @asaplog_post_dec
1003 def flag(self):
1004 if not self._any_selection():
1005 msg = "No selection to be Flagged"
1006 asaplog.post()
1007 asaplog.push(msg)
1008 asaplog.post('WARN')
1009 return
1010 self._pause_buttons(operation="start",msg="Flagging data...")
1011 self._flag_operation(rows=self._selpanels,
1012 regions=self._selregions,unflag=False)
1013 sout = "Flagged:\n"
1014 sout += " rows = "+str(self._selpanels)+"\n"
1015 sout += " regions: "+str(self._selregions)
1016 asaplog.push(sout)
1017 del sout
1018 self.plotter._ismodified = True
1019 self._clearup_selections(refresh=False)
1020 self._plot_page(pagemode="current")
1021 self._pause_buttons(operation="end")
1022
1023 ### unflag selected spectra/regions
1024 @asaplog_post_dec
1025 def unflag(self):
1026 if not self._any_selection():
1027 msg = "No selection to be Flagged"
1028 asaplog.push(msg)
1029 asaplog.post('WARN')
1030 return
1031 self._pause_buttons(operation="start",msg="Unflagging data...")
1032 self._flag_operation(rows=self._selpanels,
1033 regions=self._selregions,unflag=True)
1034 sout = "Unflagged:\n"
1035 sout += " rows = "+str(self._selpanels)+"\n"
1036 sout += " regions: "+str(self._selregions)
1037 asaplog.push(sout)
1038 del sout
1039 self.plotter._ismodified = True
1040 self._clearup_selections(refresh=False)
1041 self._plot_page(pagemode="current")
1042 self._pause_buttons(operation="end")
1043
1044 ### actual flag operation
1045 @asaplog_post_dec
1046 def _flag_operation(self,rows=None,regions=None,unflag=False):
1047 scan = self.plotter._data
1048 if not scan:
1049 asaplog.post()
1050 asaplog.push("Invalid scantable")
1051 asaplog.post("ERROR")
1052 if isinstance(rows,list) and len(rows) > 0:
1053 scan.flag_row(rows=rows,unflag=unflag)
1054 if isinstance(regions,dict) and len(regions) > 0:
1055 for srow, masklist in regions.iteritems():
1056 if not isinstance(masklist,list) or len(masklist) ==0:
1057 msg = "Ignoring invalid region selection for row = "+srow
1058 asaplog.post()
1059 asaplog.push(msg)
1060 asaplog.post("WARN")
1061 continue
1062 irow = int(srow)
1063 mask = scan.create_mask(masklist,invert=False,row=irow)
1064 scan.flag(row=irow,mask=mask,unflag=unflag)
1065 del irow, mask
1066 del srow, masklist
1067 del scan
1068
1069 ### show statistics of selected spectra/regions
1070 @asaplog_post_dec
1071 def stat_cal(self):
1072 if not self._any_selection():
1073 msg = "No selection to be calculated"
1074 asaplog.push(msg)
1075 asaplog.post('WARN')
1076 return
1077 self._selected_stats(rows=self._selpanels,regions=self._selregions)
1078 self._clearup_selections(refresh=True)
1079
1080 @asaplog_post_dec
1081 def _selected_stats(self,rows=None,regions=None):
1082 scan = self.plotter._data
1083 if not scan:
1084 asaplog.post()
1085 asaplog.push("Invalid scantable")
1086 asaplog.post("ERROR")
1087 mathobj = stmath( rcParams['insitu'] )
1088 statval = {}
1089 statstr = ['max', 'min', 'mean', 'median', 'sum', 'stddev', 'rms']
1090 if isinstance(rows, list) and len(rows) > 0:
1091 for irow in rows:
1092 for stat in statstr:
1093 statval[stat] = mathobj._statsrow(scan,[],stat,irow)[0]
1094 self._print_stats(scan,irow,statval,statstr=statstr)
1095 del irow
1096 if isinstance(regions,dict) and len(regions) > 0:
1097 for srow, masklist in regions.iteritems():
1098 if not isinstance(masklist,list) or len(masklist) ==0:
1099 msg = "Ignoring invalid region selection for row = "+srow
1100 asaplog.post()
1101 asaplog.push(msg)
1102 asaplog.post("WARN")
1103 continue
1104 irow = int(srow)
1105 mask = scan.create_mask(masklist,invert=False,row=irow)
1106 for stat in statstr:
1107 statval[stat] = mathobj._statsrow(scan,mask,stat,irow)[0]
1108 self._print_stats(scan,irow,statval,statstr=statstr,
1109 mask=masklist)
1110 del irow, mask
1111 del srow, masklist
1112 del scan, statval, mathobj
1113
1114 @asaplog_post_dec
1115 def _print_stats(self,scan,row,stats,statstr=None,mask=None):
1116 if not isinstance(scan, scantable):
1117 asaplog.post()
1118 asaplog.push("Invalid scantable")
1119 asaplog.post("ERROR")
1120 if row < 0 or row > scan.nrow():
1121 asaplog.post()
1122 asaplog.push("Invalid row number")
1123 asaplog.post("ERROR")
1124 if not isinstance(stats,dict) or len(stats) == 0:
1125 asaplog.post()
1126 asaplog.push("Invalid statistic value")
1127 asaplog.post("ERROR")
1128 maskstr = "All"
1129 if mask:
1130 maskstr = str(mask)
1131 ssep = "-"*70+"\n"
1132 sout = ssep
1133 sout += ("Row=%d Scan=%d IF=%d Pol=%d Time=%s mask=%s" % \
1134 (row, scan.getscan(row), scan.getif(row), scan.getpol(row), scan.get_time(row),maskstr))
1135 sout += "\n"
1136 statvals = []
1137 if not len(statstr):
1138 statstr = stats.keys()
1139 for key in statstr:
1140 sout += key.ljust(10)
1141 statvals.append(stats.pop(key))
1142 sout += "\n"
1143 sout += ("%f "*len(statstr) % tuple(statvals))
1144 sout += "\n"+ssep
1145 asaplog.push(sout)
1146 del sout, ssep, maskstr, statvals, key, scan, row, stats, statstr, mask
1147
1148 ### Page chages
1149 ### go to the previous page
1150 def prev_page(self):
1151 self._pause_buttons(operation="start",msg='plotting the previous page')
1152 self._clear_selection_plot(refresh=False)
1153 self._plot_page(pagemode="prev")
1154 self._plot_selections()
1155 self._pause_buttons(operation="end")
1156
1157 ### go to the next page
1158 def next_page(self):
1159 self._pause_buttons(operation="start",msg='plotting the next page')
1160 self._clear_selection_plot(refresh=False)
1161 self._plot_page(pagemode="next")
1162 self._plot_selections()
1163 self._pause_buttons(operation="end")
1164
1165 ### actual plotting of the new page
1166 def _plot_page(self,pagemode="next"):
1167 if self.plotter._startrow <= 0:
1168 msg = "The page counter is reset due to chages of plot settings. "
1169 msg += "Plotting from the first page."
1170 asaplog.post()
1171 asaplog.push(msg)
1172 asaplog.post('WARN')
1173 goback = False
1174
1175 self.plotter._plotter.hold()
1176 #self.plotter._plotter.legend(1)
1177 self._set_plot_counter(pagemode)
1178 self.plotter._plot(self.plotter._data)
1179 self.set_pagecounter(self._get_pagenum())
1180 self.plotter._plotter.release()
1181 self.plotter._plotter.tidy()
1182 self.plotter._plotter.show(hardrefresh=False)
1183
1184 ### calculate the panel ID and start row to plot a page
1185 #def _set_prevpage_counter(self):
1186 def _set_plot_counter(self, pagemode):
1187 ## page operation should be either "previous", "current", or "next"
1188 availpage = ["p","c","n"]
1189 pageop = pagemode[0].lower()
1190 if not (pageop in availpage):
1191 asaplog.post()
1192 asaplog.push("Invalid page operation")
1193 asaplog.post("ERROR")
1194 if pageop == "n":
1195 # nothing necessary to plot the next page
1196 return
1197 # set row and panel counters to those of the 1st panel of previous page
1198 maxpanel = 25
1199 # the ID of the last panel in current plot
1200 lastpanel = self.plotter._ipanel
1201 # the number of current subplots
1202 currpnum = len(self.plotter._plotter.subplots)
1203
1204 # the nuber of previous subplots
1205 start_ipanel = None
1206 if pageop == "c":
1207 start_ipanel = max(lastpanel-currpnum+1, 0)
1208 else:
1209 ## previous page
1210 prevpnum = None
1211 if self.plotter._rows and self.plotter._cols:
1212 # when user set layout
1213 prevpnum = self.plotter._rows*self.plotter._cols
1214 else:
1215 # no user specification
1216 prevpnum = maxpanel
1217 start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
1218 del prevpnum
1219
1220 # set the pannel ID of the last panel of the prev(-prev) page
1221 self.plotter._ipanel = start_ipanel-1
1222 if self.plotter._panelling == 'r':
1223 self.plotter._startrow = start_ipanel
1224 else:
1225 # the start row number of the next panel
1226 self.plotter._startrow = self.plotter._panelrows[start_ipanel]
1227 del lastpanel,currpnum,start_ipanel
1228
1229 ### refresh the page counter
1230 def set_pagecounter(self,page):
1231 nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
1232 nwidth = max(nwidth,4)
1233 formatstr = '%'+str(nwidth)+'d'
1234 self.show_pagenum(page,formatstr)
1235
1236 def show_pagenum(self,pagenum,formatstr):
1237 # passed to backend dependent class
1238 pass
1239
1240 def _get_pagenum(self):
1241 # get the ID of last panel in the current page
1242 idlastpanel = self.plotter._ipanel
1243 # max panels in a page
1244 ppp = self.plotter._plotter.rows*self.plotter._plotter.cols
1245 return int(idlastpanel/ppp)+1
1246
1247 # pause buttons for slow operations. implemented at a backend dependent class
1248 def _pause_buttons(self,operation="end",msg=""):
1249 pass
Note: See TracBrowser for help on using the repository browser.