source: trunk/python/interactivemask.py @ 3142

Last change on this file since 3142 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
RevLine 
[1826]1from asap.parameters import rcParams
2from asap.utils import _n_bools, mask_and, mask_or
[1460]3from asap.scantable import scantable
[2108]4from asap.logging import asaplog, asaplog_post_dec
[1460]5
6class interactivemask:
[2109]7    """
8    The class for interactive mask selection.
[1460]9
[2109]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()
[1826]18
[2109]19    Modify mask region by selecting a region on a plot with mouse.
20    """
[1460]21
[2109]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.
[1621]26
[2109]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)
[1621]42
[2117]43        self.scan = None
44        self.p = None
45        self.newplot = False
[2109]46        if scan and isinstance(scan, scantable):
[2117]47            self.scan = scan
[2109]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):
[2117]52                self.scan = self.p._data
[2109]53        if self.scan is None:
54            msg = "Invalid scantable."
55            raise TypeError(msg)
[1621]56
[2427]57        self.mask = _n_bools(self.scan.nchan(self.scan.getif(0)),True)
[2117]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 = []
[1621]68
[1826]69
[2109]70    def set_basemask(self,masklist=[],invert=False):
71        """
72        Set initial channel mask.
[1826]73
[2109]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)
[1460]89
[2109]90        # Create base mask
91        if ( len(masklist) > 0 ):
[2117]92            self.mask = self.scan.create_mask(masklist,invert=invert)
93        elif invert == True:
[2427]94            self.mask = _n_bools(self.scan.nchan(self.scan.getif(0)),False)
[2109]95        else:
[2427]96            self.mask = _n_bools(self.scan.nchan(self.scan.getif(0)),True)
[1460]97
[1621]98
[2109]99    def set_startevent(self,event):
100        """
101        Inherit an event from the parent function.
[1826]102
[2109]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
[2117]108        if isinstance(event,MouseEvent) and event.name == 'button_press_event':
109            self.event = event
[2109]110        else:
[2117]111            msg = "Invalid event."
[2109]112            raise TypeError(msg)
[1621]113
[2109]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        """
[2117]122        self.callback = callback
[1621]123
[2109]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.
[1621]131
[2109]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
[1460]147
[2109]148        self.once = once
149        if self.once:
[2117]150            self.showmask = showmask
[2109]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")
[2117]156            self.showmask = True
[1621]157
[2451]158        if not self.p:
[2109]159            asaplog.push('A new ASAP plotter will be loaded')
160            asaplog.post()
161            from asap.asapplotter import asapplotter
[2117]162            self.p = asapplotter()
163            self.newplot = True
[2453]164        self.p._assert_plotter(action='reload')
[2538]165        from matplotlib import rc as rcp
166        rcp('lines', linewidth=1)
[2451]167       
[2109]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
[2451]175            self.p._legendloc = 1
[2109]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()
[1460]188
[2109]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.'
[1460]198
[1621]199
[2109]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)
[1621]205
206
[2109]207    def _region_start(self,event):
208        # Do not fire event when in zooming/panning mode
209        mode = self.p._plotter.figmgr.toolbar.mode
[2117]210        if not mode == '':
[2109]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)
[1460]223
[2109]224    def _region_draw(self,event):
225        sameaxes=(event.inaxes == self.rect['axes'])
226        if sameaxes:
[2117]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
[2109]233        else:
[2117]234            xnow = self.xold
235            ynow = self.yold
[1460]236
[2109]237        self.p._plotter.figmgr.toolbar.draw_rubberband(event, xnow, ynow, self.rect['x'], self.rect['y'])
[1621]238
[2109]239    def _region_end(self,event):
240        self.p._plotter.register('motion_notify', None)
241        self.p._plotter.register('button_release', None)
[1621]242
[2109]243        # Delete the rubber band
244        self.p._plotter.figmgr.toolbar.release(event)
[1826]245
[2109]246        if event.inaxes == self.rect['axes']:
[2117]247            xend = event.x
248            yend = event.y
249            xdataend = event.xdata
250            ydataend = event.ydata
[2109]251        else:
[2117]252            xend = self.xold
253            yend = self.yold
254            xdataend = self.xdataold
255            ydataend = self.ydataold
[1826]256
[2109]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
[2117]261        self.rect = {}
262        self.xold = None
263        self.yold = None
264        self.xdataold = None
265        self.ydataold = None
[2109]266        if self.once: self.finish_selection(callback=self.callback)
[1460]267
[2109]268    def _update_mask(self):
269        # Min and Max for new mask
[2117]270        xstart = self.rect['world'][0]
271        xend = self.rect['world'][2]
[2109]272        if xstart <= xend: newlist=[xstart,xend]
[2117]273        else: newlist = [xend,xstart]
[2109]274        # Mask or unmask
[2117]275        invmask = None
[2109]276        if self.rect['button'] == 1:
[2117]277            invmask = False
278            mflg = 'Mask'
[2109]279        elif self.rect['button'] == 3:
[2117]280            invmask = True
281            mflg = 'UNmask'
[2109]282        asaplog.push(mflg+': '+str(newlist))
283        asaplog.post()
[2117]284        newmask = self.scan.create_mask(newlist,invert=invmask)
[2109]285        # Logic operation to update mask
286        if invmask:
[2117]287            self.mask = mask_and(self.mask,newmask)
[2109]288        else:
[2117]289            self.mask = mask_or(self.mask,newmask)
[2109]290        # Plot masked regions
291        #if self.showmask or not self.once: self._plot_mask()
292        if self.showmask: self._plot_mask()
[1460]293
[2109]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
[2117]299        ifs = self.scan.getifnos()
[2109]300        projs = []
301        if len(ifs) > 1:
[2117]302            row0if = self.scan.getif(0)
[2109]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()
[2117]312            self._polygons = []
[2109]313        # Plot new polygons
314        if len(msks) > 0:
[2117]315            npanel = len(self.p._plotter.subplots)
316            j = -1
[2109]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()
[1460]325
[2109]326    def finish_selection(self, callback=None):
327        """
328        Execute callback function, reset or close plotter window as
329        necessary.
[1623]330
[2109]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)
[1826]336
[2109]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()
[2538]342        if not self.event:
343            try: self.p._plotter.register('button_press',None)
344            except: pass # plotter window is closed by X button.
[2109]345        # Finish the plot
346        if not self.newplot:
347            self.clear_polygon()
348        else:
[2538]349            #self.p._plotter.unmap()
350            self.p._plotter.quit()
[2109]351            self.p._plotter = None
352            del self.p
[2117]353            self.p = None
354            self._polygons = []
[1621]355
356
[2109]357    def clear_polygon(self):
358        """
359        Erase masks plots from the plotter.
360        """
[2117]361        if len(self._polygons) > 0:
[2109]362            # Remove old polygons
363            for polygon in self._polygons: polygon.remove()
364            self.p._plotter.show()
[2117]365            self._polygons = []
[1621]366
367
[2109]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.