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