source: branches/parallel/python/interactivemask.py@ 2308

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

merged bug fixes in trunk (r2108)

File size: 10.5 KB
Line 
1from asap.parameters import rcParams
2from asap.utils import _n_bools, mask_and, mask_or
3from asap.scantable import scantable
4from asap.logging import asaplog, asaplog_post_dec
5
6class interactivemask:
7 """
8 The class for interactive mask selection.
9
10 Example:
11 my_mask=interactivemask(plotter,scan)
12 my_mask.set_basemask(masklist=[[0,10],[90,100]],invert=False)
13 # Do interactive mask selection
14 my_mask.select_mask()
15 finish=raw_input('Press return to finish selection.\n')
16 my_mask.finish_selection(callback=func)
17 mask=my_mask.get_mask()
18
19 Modify mask region by selecting a region on a plot with mouse.
20 """
21
22 def __init__(self,plotter=None, scan=None):
23 """
24 Create a interactive masking object.
25 Either or both 'plotter' or/and 'scan' should be defined.
26
27 Parameters:
28 plotter: an ASAP plotter object for interactive selection
29 scan: a scantable to create a mask interactively
30 """
31 # Return if GUI is not active
32 if not rcParams['plotter.gui']:
33 msg = 'GUI plotter is disabled.\n'
34 msg += 'Exit interactive mode.'
35 asaplog.push(msg)
36 asaplog.post("ERROR")
37 return
38 # Verify input parameters
39 if scan is None and plotter is None:
40 msg = "Either scantable or plotter should be defined."
41 raise TypeError(msg)
42
43 self.scan=None
44 self.p=None
45 self.newplot=False
46 if scan and isinstance(scan, scantable):
47 self.scan=scan
48 from asap.asapplotter import asapplotter
49 if plotter and isinstance(plotter,asapplotter):
50 self.p = plotter
51 if self.scan is None and isinstance(self.p._data,scantable):
52 self.scan=self.p._data
53 if self.scan is None:
54 msg = "Invalid scantable."
55 raise TypeError(msg)
56
57 self.mask=_n_bools(self.scan.nchan(),True)
58 self.callback=None
59 self.event=None
60 self.once=False
61 self.showmask=True
62 self.rect={}
63 self.xold=None
64 self.yold=None
65 self.xdataold=None
66 self.ydataold=None
67 self._polygons=[]
68
69
70 def set_basemask(self,masklist=[],invert=False):
71 """
72 Set initial channel mask.
73
74 Parameters:
75 masklist: [[min, max], [min2, max2], ...]
76 A list of pairs of start/end points (inclusive)
77 specifying the regions to be masked
78 invert: optional argument. If specified as True,
79 return an inverted mask, i.e. the regions
80 specified are excluded
81 You can reset the mask selection by running this method with
82 the default parameters.
83 """
84 # Verify input parameters
85 if not (isinstance(masklist, list) or isinstance(masklist, tuple)) \
86 or not isinstance(invert, bool):
87 msg = 'Invalid mask definition'
88 raise TypeError(msg)
89
90 # Create base mask
91 if ( len(masklist) > 0 ):
92 self.mask=self.scan.create_mask(masklist,invert=invert)
93 elif invert==True:
94 self.mask=_n_bools(self.scan.nchan(),False)
95 else:
96 self.mask=_n_bools(self.scan.nchan(),True)
97
98
99 def set_startevent(self,event):
100 """
101 Inherit an event from the parent function.
102
103 Parameters:
104 event: 'button_press_event' object to be inherited to
105 start interactive region selection .
106 """
107 from matplotlib.backend_bases import MouseEvent
108 if isinstance(event,MouseEvent) and event.name=='button_press_event':
109 self.event=event
110 else:
111 msg="Invalid event."
112 raise TypeError(msg)
113
114 def set_callback(self,callback):
115 """
116 Set callback function to run when finish_selection() is executed.
117 callback: The post processing function to run after
118 the mask selections are completed.
119 This will be overwritten if callback is defined in
120 finish_selection(callback=func)
121 """
122 self.callback=callback
123
124 def select_mask(self,once=False,showmask=True):
125 """
126 Do interactive mask selection.
127 Modify masks interactively by adding/deleting regions with
128 mouse drawing.(left-button: mask; right-button: UNmask)
129 Note that the interactive region selection is available only
130 when GUI plotter is active.
131
132 Parameters:
133 once: If specified as True, you can modify masks only
134 once. Else if False, you can modify them repeatedly.
135 showmask: If specified as True, the masked regions are plotted
136 on the plotter.
137 Note this parameter is valid only when once=True.
138 Otherwise, maskes are forced to be plotted for reference.
139 """
140 # Return if GUI is not active
141 if not rcParams['plotter.gui']:
142 msg = 'GUI plotter is disabled.\n'
143 msg += 'Exit interactive mode.'
144 asaplog.push(msg)
145 asaplog.post("ERROR")
146 return
147
148 self.once = once
149 if self.once:
150 self.showmask=showmask
151 else:
152 if not showmask:
153 asaplog.post()
154 asaplog.push('showmask spcification is ignored. Mask regions are plotted anyway.')
155 asaplog.post("WARN")
156 self.showmask=True
157
158 #if not self.p._plotter or self.p._plotter.is_dead:
159 if not self.p or self.p._plotter.is_dead:
160 asaplog.push('A new ASAP plotter will be loaded')
161 asaplog.post()
162 from asap.asapplotter import asapplotter
163 self.p=asapplotter()
164 self.newplot=True
165
166 # Plot selected spectra if needed
167 if self.scan != self.p._data:
168 if len(self.scan.getifnos()) > 16:
169 asaplog.post()
170 asaplog.push("Number of panels > 16. Plotting the first 16...")
171 asaplog.post("WARN")
172 # Need replot
173 self.p._plotter.legend(1)
174 self.p.plot(self.scan)
175 # disable casa toolbar
176 if self.p._plotter.figmgr.casabar:
177 self.p._plotter.figmgr.casabar.disable_button()
178 self.p._plotter.figmgr.casabar.disable_prev()
179 self.p._plotter.figmgr.casabar.disable_next()
180 for panel in self.p._plotter.subplots:
181 xmin, xmax = panel['axes'].get_xlim()
182 marg = 0.05*abs(xmax-xmin)
183 panel['axes'].set_xlim(xmin-marg, xmax+marg)
184 if rcParams['plotter.ganged']: break
185 self.p._plotter.show()
186
187 # Plot initial mask region
188 #if self.showmask or not self.once:
189 if self.showmask:
190 self._plot_mask()
191 print ''
192 print 'Selected regions are shaded with yellow. (gray: projections)'
193 print 'Now you can modify the selection.'
194 print 'Draw rectangles with Left-mouse to add the regions,'
195 print 'or with Right-mouse to exclude the regions.'
196
197
198 if self.event != None:
199 self._region_start(self.event)
200 else:
201 self.p._plotter.register('button_press',None)
202 self.p._plotter.register('button_press',self._region_start)
203
204
205 def _region_start(self,event):
206 # Do not fire event when in zooming/panning mode
207 mode = self.p._plotter.figmgr.toolbar.mode
208 if not mode =='':
209 return
210 # Return if selected point is out of panel
211 if event.inaxes == None: return
212 # Select mask/unmask region with mask
213 self.rect = {'button': event.button, 'axes': event.inaxes,
214 'x': event.x, 'y': event.y,
215 'world': [event.xdata, event.ydata,
216 event.xdata, event.ydata],
217 'pixel': [event.x, event.y,
218 event.x, event.y]}
219 self.p._plotter.register('motion_notify', self._region_draw)
220 self.p._plotter.register('button_release', self._region_end)
221
222 def _region_draw(self,event):
223 sameaxes=(event.inaxes == self.rect['axes'])
224 if sameaxes:
225 xnow=event.x
226 ynow=event.y
227 self.xold=xnow
228 self.yold=ynow
229 self.xdataold=event.xdata
230 self.ydataold=event.ydata
231 else:
232 xnow=self.xold
233 ynow=self.yold
234
235 self.p._plotter.figmgr.toolbar.draw_rubberband(event, xnow, ynow, self.rect['x'], self.rect['y'])
236
237 def _region_end(self,event):
238 self.p._plotter.register('motion_notify', None)
239 self.p._plotter.register('button_release', None)
240
241 # Delete the rubber band
242 self.p._plotter.figmgr.toolbar.release(event)
243
244 if event.inaxes == self.rect['axes']:
245 xend=event.x
246 yend=event.y
247 xdataend=event.xdata
248 ydataend=event.ydata
249 else:
250 xend=self.xold
251 yend=self.yold
252 xdataend=self.xdataold
253 ydataend=self.ydataold
254
255 self.rect['world'][2:4] = [xdataend, ydataend]
256 self.rect['pixel'][2:4] = [xend, yend]
257 self._update_mask()
258 # Clear up region selection
259 self.rect={}
260 self.xold=None
261 self.yold=None
262 self.xdataold=None
263 self.ydataold=None
264 if self.once: self.finish_selection(callback=self.callback)
265
266 def _update_mask(self):
267 # Min and Max for new mask
268 xstart=self.rect['world'][0]
269 xend=self.rect['world'][2]
270 if xstart <= xend: newlist=[xstart,xend]
271 else: newlist=[xend,xstart]
272 # Mask or unmask
273 invmask=None
274 if self.rect['button'] == 1:
275 invmask=False
276 mflg='Mask'
277 elif self.rect['button'] == 3:
278 invmask=True
279 mflg='UNmask'
280 asaplog.push(mflg+': '+str(newlist))
281 asaplog.post()
282 newmask=self.scan.create_mask(newlist,invert=invmask)
283 # Logic operation to update mask
284 if invmask:
285 self.mask=mask_and(self.mask,newmask)
286 else:
287 self.mask=mask_or(self.mask,newmask)
288 # Plot masked regions
289 #if self.showmask or not self.once: self._plot_mask()
290 if self.showmask: self._plot_mask()
291
292 # Plot masked regions
293 def _plot_mask(self):
294 msks = []
295 msks = self.scan.get_masklist(self.mask,row=0)
296 # Get projection masks for multi-IF
297 ifs=self.scan.getifnos()
298 projs = []
299 if len(ifs) > 1:
300 row0if=self.scan.getif(0)
301 for ifno in ifs:
302 if ifno == row0if: continue
303 for row in xrange(self.scan.nrow()):
304 if self.scan.getif(row) == ifno:
305 projs.append(self.scan.get_masklist(self.mask,row=row))
306 break
307 if len(self._polygons)>0:
308 # Remove old polygons
309 for polygon in self._polygons: polygon.remove()
310 self._polygons=[]
311 # Plot new polygons
312 if len(msks) > 0:
313 npanel=len(self.p._plotter.subplots)
314 j=-1
315 for iloop in range(len(msks)*npanel):
316 i = iloop % len(msks)
317 if i == 0 : j += 1
318 if len(ifs) > 1:
319 for k in xrange(len(ifs)-1):
320 self._polygons.append(self.p._plotter.subplots[j]['axes'].axvspan(projs[k][i][0],projs[k][i][1],facecolor='#aaaaaa'))
321 self._polygons.append(self.p._plotter.subplots[j]['axes'].axvspan(msks[i][0],msks[i][1],facecolor='yellow'))
322 self.p._plotter.canvas.draw()
323
324 def finish_selection(self, callback=None):
325 """
326 Execute callback function, reset or close plotter window as
327 necessary.
328
329 Parameters:
330 callback: The post processing function to run after
331 the mask selections are completed.
332 Specifying the callback function here will overwrite
333 the one set by set_callback(func)
334
335 Note this function is automatically called at the end of
336 select_mask() if once=True.
337 """
338 if callback: self.callback=callback
339 if self.callback: self.callback()
340 if not self.event: self.p._plotter.register('button_press',None)
341 # Finish the plot
342 if not self.newplot:
343 self.clear_polygon()
344 else:
345 self.p._plotter.unmap()
346 self.p._plotter = None
347 del self.p
348 self.p=None
349 self._polygons=[]
350
351
352 def clear_polygon(self):
353 """
354 Erase masks plots from the plotter.
355 """
356 if len(self._polygons)>0:
357 # Remove old polygons
358 for polygon in self._polygons: polygon.remove()
359 self.p._plotter.show()
360 self._polygons=[]
361
362
363 def get_mask(self):
364 """
365 Get the interactively selected channel mask.
366 Returns:
367 A list of channel mask.
368 """
369 return self.mask
Note: See TracBrowser for help on using the repository browser.