source: trunk/python/interactivemask.py@ 2631

Last change on this file since 2631 was 2538, checked in by Kana Sugimoto, 13 years ago

New Development: No

JIRA Issue: Yes (related to CAS-3749)

Ready for Test: Yes

Interface Changes: Yes

What Interface Changed: added a new method, _alive(), in asaplotbase class

Test Programs: Interactive tests

Put in Release Notes: No

Module(s):

Description:

Added a new method, _alive(), in asaplotbase class. The method return True
if asaplot instance is alive. More complete check compared to 'is_dead' parameter.
asapplotter._assert_plotter method is simplified by calling this function.

Fixed misc bugs found in interactive tests.

  1. set back linewidth = 1 in plots invoked by modules, interactivemask, asapmath, and asapfitter.
  2. interactivemask module: plotter in properly quited at the end.
  3. interactivemask module: avoid error when user close interacive plot window before calling the finalization method, finish_selection().
  4. added definition of a dummy function, quit(), in asaplot class.


File size: 13.3 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(self.scan.getif(0)),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(self.scan.getif(0)),False)
95 else:
96 self.mask = _n_bools(self.scan.nchan(self.scan.getif(0)),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:
159 asaplog.push('A new ASAP plotter will be loaded')
160 asaplog.post()
161 from asap.asapplotter import asapplotter
162 self.p = asapplotter()
163 self.newplot = True
164 self.p._assert_plotter(action='reload')
165 from matplotlib import rc as rcp
166 rcp('lines', linewidth=1)
167
168 # Plot selected spectra if needed
169 if self.scan != self.p._data:
170 if len(self.scan.getifnos()) > 16:
171 asaplog.post()
172 asaplog.push("Number of panels > 16. Plotting the first 16...")
173 asaplog.post("WARN")
174 # Need replot
175 self.p._legendloc = 1
176 self.p.plot(self.scan)
177 # disable casa toolbar
178 if self.p._plotter.figmgr.casabar:
179 self.p._plotter.figmgr.casabar.disable_button()
180 self.p._plotter.figmgr.casabar.disable_prev()
181 self.p._plotter.figmgr.casabar.disable_next()
182 for panel in self.p._plotter.subplots:
183 xmin, xmax = panel['axes'].get_xlim()
184 marg = 0.05*abs(xmax-xmin)
185 panel['axes'].set_xlim(xmin-marg, xmax+marg)
186 if rcParams['plotter.ganged']: break
187 self.p._plotter.show()
188
189 # Plot initial mask region
190 #if self.showmask or not self.once:
191 if self.showmask:
192 self._plot_mask()
193 print ''
194 print 'Selected regions are shaded with yellow. (gray: projections)'
195 print 'Now you can modify the selection.'
196 print 'Draw rectangles with Left-mouse to add the regions,'
197 print 'or with Right-mouse to exclude the regions.'
198
199
200 if self.event != None:
201 self._region_start(self.event)
202 else:
203 self.p._plotter.register('button_press',None)
204 self.p._plotter.register('button_press',self._region_start)
205
206
207 def _region_start(self,event):
208 # Do not fire event when in zooming/panning mode
209 mode = self.p._plotter.figmgr.toolbar.mode
210 if not mode == '':
211 return
212 # Return if selected point is out of panel
213 if event.inaxes == None: return
214 # Select mask/unmask region with mask
215 self.rect = {'button': event.button, 'axes': event.inaxes,
216 'x': event.x, 'y': event.y,
217 'world': [event.xdata, event.ydata,
218 event.xdata, event.ydata],
219 'pixel': [event.x, event.y,
220 event.x, event.y]}
221 self.p._plotter.register('motion_notify', self._region_draw)
222 self.p._plotter.register('button_release', self._region_end)
223
224 def _region_draw(self,event):
225 sameaxes=(event.inaxes == self.rect['axes'])
226 if sameaxes:
227 xnow = event.x
228 ynow = event.y
229 self.xold = xnow
230 self.yold = ynow
231 self.xdataold = event.xdata
232 self.ydataold = event.ydata
233 else:
234 xnow = self.xold
235 ynow = self.yold
236
237 self.p._plotter.figmgr.toolbar.draw_rubberband(event, xnow, ynow, self.rect['x'], self.rect['y'])
238
239 def _region_end(self,event):
240 self.p._plotter.register('motion_notify', None)
241 self.p._plotter.register('button_release', None)
242
243 # Delete the rubber band
244 self.p._plotter.figmgr.toolbar.release(event)
245
246 if event.inaxes == self.rect['axes']:
247 xend = event.x
248 yend = event.y
249 xdataend = event.xdata
250 ydataend = event.ydata
251 else:
252 xend = self.xold
253 yend = self.yold
254 xdataend = self.xdataold
255 ydataend = self.ydataold
256
257 self.rect['world'][2:4] = [xdataend, ydataend]
258 self.rect['pixel'][2:4] = [xend, yend]
259 self._update_mask()
260 # Clear up region selection
261 self.rect = {}
262 self.xold = None
263 self.yold = None
264 self.xdataold = None
265 self.ydataold = None
266 if self.once: self.finish_selection(callback=self.callback)
267
268 def _update_mask(self):
269 # Min and Max for new mask
270 xstart = self.rect['world'][0]
271 xend = self.rect['world'][2]
272 if xstart <= xend: newlist=[xstart,xend]
273 else: newlist = [xend,xstart]
274 # Mask or unmask
275 invmask = None
276 if self.rect['button'] == 1:
277 invmask = False
278 mflg = 'Mask'
279 elif self.rect['button'] == 3:
280 invmask = True
281 mflg = 'UNmask'
282 asaplog.push(mflg+': '+str(newlist))
283 asaplog.post()
284 newmask = self.scan.create_mask(newlist,invert=invmask)
285 # Logic operation to update mask
286 if invmask:
287 self.mask = mask_and(self.mask,newmask)
288 else:
289 self.mask = mask_or(self.mask,newmask)
290 # Plot masked regions
291 #if self.showmask or not self.once: self._plot_mask()
292 if self.showmask: self._plot_mask()
293
294 # Plot masked regions
295 def _plot_mask(self):
296 msks = []
297 msks = self.scan.get_masklist(self.mask,row=0)
298 # Get projection masks for multi-IF
299 ifs = self.scan.getifnos()
300 projs = []
301 if len(ifs) > 1:
302 row0if = self.scan.getif(0)
303 for ifno in ifs:
304 if ifno == row0if: continue
305 for row in xrange(self.scan.nrow()):
306 if self.scan.getif(row) == ifno:
307 projs.append(self.scan.get_masklist(self.mask,row=row))
308 break
309 if len(self._polygons)>0:
310 # Remove old polygons
311 for polygon in self._polygons: polygon.remove()
312 self._polygons = []
313 # Plot new polygons
314 if len(msks) > 0:
315 npanel = len(self.p._plotter.subplots)
316 j = -1
317 for iloop in range(len(msks)*npanel):
318 i = iloop % len(msks)
319 if i == 0 : j += 1
320 if len(ifs) > 1:
321 for k in xrange(len(ifs)-1):
322 self._polygons.append(self.p._plotter.subplots[j]['axes'].axvspan(projs[k][i][0],projs[k][i][1],facecolor='#aaaaaa'))
323 self._polygons.append(self.p._plotter.subplots[j]['axes'].axvspan(msks[i][0],msks[i][1],facecolor='yellow'))
324 self.p._plotter.canvas.draw()
325
326 def finish_selection(self, callback=None):
327 """
328 Execute callback function, reset or close plotter window as
329 necessary.
330
331 Parameters:
332 callback: The post processing function to run after
333 the mask selections are completed.
334 Specifying the callback function here will overwrite
335 the one set by set_callback(func)
336
337 Note this function is automatically called at the end of
338 select_mask() if once=True.
339 """
340 if callback: self.callback=callback
341 if self.callback: self.callback()
342 if not self.event:
343 try: self.p._plotter.register('button_press',None)
344 except: pass # plotter window is closed by X button.
345 # Finish the plot
346 if not self.newplot:
347 self.clear_polygon()
348 else:
349 #self.p._plotter.unmap()
350 self.p._plotter.quit()
351 self.p._plotter = None
352 del self.p
353 self.p = None
354 self._polygons = []
355
356
357 def clear_polygon(self):
358 """
359 Erase masks plots from the plotter.
360 """
361 if len(self._polygons) > 0:
362 # Remove old polygons
363 for polygon in self._polygons: polygon.remove()
364 self.p._plotter.show()
365 self._polygons = []
366
367
368 def get_mask(self):
369 """
370 Get the interactively selected channel mask.
371 Returns:
372 A list of channel mask.
373 """
374 return self.mask
Note: See TracBrowser for help on using the repository browser.