source: trunk/python/interactivemask.py @ 2451

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

New Development: No

JIRA Issue: Yes (CAS-3749)

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs: unit tests of sdplot

Put in Release Notes: No

Module(s): sdplot, sdfit, sdstat, sdflag, sdcal, sdreduce

Description:

Made asapplotter not to generate plotter window at start-up, but the window is
only generated at the first invokation of plotting operation.


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