source: trunk/python/interactivemask.py@ 2475

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

New Development: No

JIRA Issue: Yes (CAS-3749/Trac-266)

Ready for Test: Yes

Interface Changes: Yes

What Interface Changed: renamed a parameter of asapplotter._assert_plotter from mode to action

Test Programs:

Put in Release Notes: No

Module(s):

Description: renamed a parameter of asapplotter._assert_plotter from mode to action


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