source: trunk/python/interactivemask.py

Last change on this file was 2538, checked in by Kana Sugimoto, 12 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.