source: trunk/python/interactivemask.py @ 2427

Last change on this file since 2427 was 2427, checked in by Kana Sugimoto, 12 years ago

New Development: No

JIRA Issue: Yes (CAS-3758)

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs: interactive test with

Put in Release Notes: No

Module(s): sdbaseline, sdfit, sdstat

Description:

Interactivemask module looks for the number of channels in the first
IF to create mask instead of the number recorded in scantable header.
Changed minimum number of channels required from 2 to 1 in
scantable.get_masklist, scantable.get_mask_indices,
Scantable::getMaskRangeList, and Scantable::getMaskEdgeIndices,
because it is not mandatory to have multiple channels in these functions.


File size: 13.2 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
[2109]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
[2117]163            self.p = asapplotter()
164            self.newplot = True
[1460]165
[2109]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()
[1460]186
[2109]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.'
[1460]196
[1621]197
[2109]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)
[1621]203
204
[2109]205    def _region_start(self,event):
206        # Do not fire event when in zooming/panning mode
207        mode = self.p._plotter.figmgr.toolbar.mode
[2117]208        if not mode == '':
[2109]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)
[1460]221
[2109]222    def _region_draw(self,event):
223        sameaxes=(event.inaxes == self.rect['axes'])
224        if sameaxes:
[2117]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
[2109]231        else:
[2117]232            xnow = self.xold
233            ynow = self.yold
[1460]234
[2109]235        self.p._plotter.figmgr.toolbar.draw_rubberband(event, xnow, ynow, self.rect['x'], self.rect['y'])
[1621]236
[2109]237    def _region_end(self,event):
238        self.p._plotter.register('motion_notify', None)
239        self.p._plotter.register('button_release', None)
[1621]240
[2109]241        # Delete the rubber band
242        self.p._plotter.figmgr.toolbar.release(event)
[1826]243
[2109]244        if event.inaxes == self.rect['axes']:
[2117]245            xend = event.x
246            yend = event.y
247            xdataend = event.xdata
248            ydataend = event.ydata
[2109]249        else:
[2117]250            xend = self.xold
251            yend = self.yold
252            xdataend = self.xdataold
253            ydataend = self.ydataold
[1826]254
[2109]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
[2117]259        self.rect = {}
260        self.xold = None
261        self.yold = None
262        self.xdataold = None
263        self.ydataold = None
[2109]264        if self.once: self.finish_selection(callback=self.callback)
[1460]265
[2109]266    def _update_mask(self):
267        # Min and Max for new mask
[2117]268        xstart = self.rect['world'][0]
269        xend = self.rect['world'][2]
[2109]270        if xstart <= xend: newlist=[xstart,xend]
[2117]271        else: newlist = [xend,xstart]
[2109]272        # Mask or unmask
[2117]273        invmask = None
[2109]274        if self.rect['button'] == 1:
[2117]275            invmask = False
276            mflg = 'Mask'
[2109]277        elif self.rect['button'] == 3:
[2117]278            invmask = True
279            mflg = 'UNmask'
[2109]280        asaplog.push(mflg+': '+str(newlist))
281        asaplog.post()
[2117]282        newmask = self.scan.create_mask(newlist,invert=invmask)
[2109]283        # Logic operation to update mask
284        if invmask:
[2117]285            self.mask = mask_and(self.mask,newmask)
[2109]286        else:
[2117]287            self.mask = mask_or(self.mask,newmask)
[2109]288        # Plot masked regions
289        #if self.showmask or not self.once: self._plot_mask()
290        if self.showmask: self._plot_mask()
[1460]291
[2109]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
[2117]297        ifs = self.scan.getifnos()
[2109]298        projs = []
299        if len(ifs) > 1:
[2117]300            row0if = self.scan.getif(0)
[2109]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()
[2117]310            self._polygons = []
[2109]311        # Plot new polygons
312        if len(msks) > 0:
[2117]313            npanel = len(self.p._plotter.subplots)
314            j = -1
[2109]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()
[1460]323
[2109]324    def finish_selection(self, callback=None):
325        """
326        Execute callback function, reset or close plotter window as
327        necessary.
[1623]328
[2109]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)
[1826]334
[2109]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
[2117]348            self.p = None
349            self._polygons = []
[1621]350
351
[2109]352    def clear_polygon(self):
353        """
354        Erase masks plots from the plotter.
355        """
[2117]356        if len(self._polygons) > 0:
[2109]357            # Remove old polygons
358            for polygon in self._polygons: polygon.remove()
359            self.p._plotter.show()
[2117]360            self._polygons = []
[1621]361
362
[2109]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.