source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/masking.py @ 2d74016

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 2d74016 was 02aff3d, checked in by Jae Cho <jhjcho@…>, 13 years ago

changed masking notes

  • Property mode set to 100644
File size: 21.6 KB
Line 
1
2################################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#
7#If you use DANSE applications to do scientific research that leads to
8#publication, we ask that you acknowledge the use of the software with the
9#following sentence:
10#
11#This work benefited from DANSE software developed under NSF award DMR-0520547.
12#
13#copyright 2008, University of Tennessee
14################################################################################
15
16
17##Todo: cleaning up, improving the maskplotpanel initialization, and testing.
18import wx
19import sys
20import pylab
21import math
22import copy
23import numpy
24from danse.common.plottools.PlotPanel import PlotPanel
25from danse.common.plottools.plottables import Graph
26from binder import BindArtist
27from sans.guiframe.dataFitting import Data2D
28from boxMask import BoxMask
29from sectorMask import SectorMask
30from sans.guiframe.events import SlicerEvent
31from sans.guiframe.events import StatusEvent
32(InternalEvent, EVT_INTERNAL) = wx.lib.newevent.NewEvent()
33
34DEFAULT_CMAP = pylab.cm.jet
35_BOX_WIDTH = 76
36_SCALE = 1e-6
37_STATICBOX_WIDTH = 380
38PANEL_SIZE = 420
39#SLD panel size
40if sys.platform.count("win32") > 0:
41    FONT_VARIANT = 0
42else:
43    FONT_VARIANT = 1
44   
45
46class MaskPanel(wx.Dialog):
47    """
48    Provides the Mask Editor GUI.
49    """
50    ## Internal nickname for the window, used by the AUI manager
51    window_name = "Mask Editor"
52    ## Name to appear on the window title bar
53    window_caption = "Mask Editor"
54    ## Flag to tell the AUI manager to put this panel in the center pane
55    CENTER_PANE = True
56    def __init__(self, parent=None, base=None, 
57                 data=None, id=-1, *args, **kwds):
58        kwds["style"] = wx.DEFAULT_DIALOG_STYLE
59        kwds["size"] = wx.Size(_STATICBOX_WIDTH * 2, PANEL_SIZE) 
60        wx.Dialog.__init__(self, parent, id=id,  *args, **kwds)
61       
62        if data != None:
63            #Font size
64            kwds = []
65            self.SetWindowVariant(variant=FONT_VARIANT)
66            self.SetTitle("Mask Editor for " + data.name)
67            self.parent = base
68            self.data = data
69            self.str = self.data.__str__()
70            ## mask for 2D
71            self.mask = data.mask
72            self.default_mask = copy.deepcopy(data.mask)
73            ## masked data from GUI
74            self.slicer_mask = None
75            self.slicer = None
76            self.slicer_z = 5
77            self.data.interactive = True
78            ## when 2 data have the same id override the 1 st plotted
79            self.name = self.data.name
80            # Panel for 2D plot
81            self.plotpanel = Maskplotpanel(self, -1,
82                                           style=wx.TRANSPARENT_WINDOW)
83            self.cmap = DEFAULT_CMAP
84            ## Create Artist and bind it
85            self.subplot = self.plotpanel.subplot
86            self.connect = BindArtist(self.subplot.figure)
87            self._setup_layout()
88            self.newplot = Data2D(image=self.data.data)
89            self.newplot.setValues(self.data)
90            self.plotpanel.add_image(self.newplot) 
91            self._update_mask(self.mask)
92            self.Centre()
93            self.Layout()
94            # bind evt_close to _draw in fitpage
95            self.Bind(wx.EVT_CLOSE, self.OnClose)
96           
97    def ShowMessage(self, msg=''):
98        """
99        Show error message when mask covers whole data area
100        """
101        mssg = 'Erase, redraw or clear the mask. \n\r'
102        mssg += 'The data range can not be completely masked... \n\r'
103        mssg += msg
104        wx.MessageBox(mssg, 'Error', wx.OK | wx.ICON_ERROR)
105   
106    def _setup_layout(self):
107        """
108        Set up the layout
109        """
110        note = "Note: This masking applies\n     only to %s." % self.data.name
111        note_txt = wx.StaticText(self, -1, note) 
112        note_txt.SetForegroundColour(wx.RED)
113        shape = "Select a Shape for Masking:"
114        #  panel
115        sizer = wx.GridBagSizer(10, 10)
116        #---------inputs----------------
117        shape_txt = wx.StaticText(self, -1, shape) 
118        sizer.Add(shape_txt, (1, 1), flag=wx.TOP|wx.LEFT|wx.BOTTOM, border=5)
119        self.innersector_rb = wx.RadioButton(self, -1, "Double Wings")
120        self.Bind(wx.EVT_RADIOBUTTON, self.onInnerSectorMask,
121                  id=self.innersector_rb.GetId())
122        sizer.Add(self.innersector_rb, (2, 1), 
123                  flag=wx.RIGHT|wx.BOTTOM, border=5)
124        self.innercircle_rb = wx.RadioButton(self, -1, "Circular Disk")
125        self.Bind(wx.EVT_RADIOBUTTON, self.onInnerRingMask,
126                  id=self.innercircle_rb.GetId())
127        sizer.Add(self.innercircle_rb, (3, 1),
128                   flag=wx.RIGHT|wx.BOTTOM, border=5)
129       
130        self.innerbox_rb = wx.RadioButton(self, -1, "Rectangular Disk")
131        self.Bind(wx.EVT_RADIOBUTTON, self.onInnerBoxMask,
132                  id=self.innerbox_rb.GetId())
133        sizer.Add(self.innerbox_rb, (4, 1), flag=wx.RIGHT|wx.BOTTOM, border=5)
134
135        self.outersector_rb = wx.RadioButton(self, -1, "Double Wing Window")
136        self.Bind(wx.EVT_RADIOBUTTON, self.onOuterSectorMask, 
137                  id=self.outersector_rb.GetId())
138        sizer.Add(self.outersector_rb, (5, 1),
139                  flag=wx.RIGHT|wx.BOTTOM, border=5)
140       
141        #outersector_y_txt = wx.StaticText(self, -1, 'Outer Sector')
142        self.outercircle_rb = wx.RadioButton(self, -1, "Circular Window")
143        self.Bind(wx.EVT_RADIOBUTTON, self.onOuterRingMask,
144                  id=self.outercircle_rb.GetId())
145        sizer.Add(self.outercircle_rb, (6, 1), 
146                  flag=wx.RIGHT|wx.BOTTOM, border=5)
147        #outerbox_txt = wx.StaticText(self, -1, 'Outer Box')
148        self.outerbox_rb = wx.RadioButton(self, -1, "Rectangular Window")
149        self.Bind(wx.EVT_RADIOBUTTON, self.onOuterBoxMask, 
150                  id=self.outerbox_rb.GetId())
151        sizer.Add(self.outerbox_rb, (7, 1), flag=wx.RIGHT|wx.BOTTOM, border=5)
152        sizer.Add(note_txt, (8, 1), flag=wx.RIGHT|wx.BOTTOM, border=5)
153        self.innercircle_rb.SetValue(False)
154        self.outercircle_rb.SetValue(False)       
155        self.innerbox_rb.SetValue(False)
156        self.outerbox_rb.SetValue(False)
157        self.innersector_rb.SetValue(False)
158        self.outersector_rb.SetValue(False)
159        sizer.Add(self.plotpanel, (0, 2), (13, 13), 
160                  wx.EXPAND|wx.LEFT|wx.RIGHT, 15)
161
162        #-----Buttons------------1
163        id_button = wx.NewId()
164        button_add = wx.Button(self, id_button, "Add")
165        button_add.SetToolTipString("Add the mask drawn.")
166        button_add.Bind(wx.EVT_BUTTON, self.onAddMask, id=button_add.GetId()) 
167        sizer.Add(button_add, (13, 7))
168        id_button = wx.NewId()
169        button_erase = wx.Button(self, id_button, "Erase")
170        button_erase.SetToolTipString("Erase the mask drawn.")
171        button_erase.Bind(wx.EVT_BUTTON, self.onEraseMask,
172                          id=button_erase.GetId()) 
173        sizer.Add(button_erase, (13, 8))
174        id_button = wx.NewId()
175        button_reset = wx.Button(self, id_button, "Reset")
176        button_reset.SetToolTipString("Reset the mask.")
177        button_reset.Bind(wx.EVT_BUTTON, self.onResetMask,
178                          id=button_reset.GetId()) 
179        sizer.Add(button_reset, (13, 9), flag=wx.RIGHT|wx.BOTTOM, border=15)
180        id_button = wx.NewId()
181        button_reset = wx.Button(self, id_button, "Clear")
182        button_reset.SetToolTipString("Clear all mask.")
183        button_reset.Bind(wx.EVT_BUTTON, self.onClearMask,
184                          id=button_reset.GetId()) 
185        sizer.Add(button_reset, (13, 10), flag=wx.RIGHT|wx.BOTTOM, border=15)
186        sizer.AddGrowableCol(3)
187        sizer.AddGrowableRow(2)
188        self.SetSizerAndFit(sizer)
189        self.Centre()
190        self.Show(True)
191
192    def onInnerBoxMask(self, event=None):
193        """
194        Call Draw Box Slicer and get mask inside of the box
195        """
196        #get ready for next evt
197        event.Skip()       
198        #from boxMask import BoxMask
199        if event != None:
200            self.onClearSlicer(event)         
201        self.slicer_z += 1
202        self.slicer =  BoxMask(self, self.subplot,
203                               zorder=self.slicer_z, side=True)
204        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
205        self.subplot.set_xlim(self.data.xmin, self.data.xmax)
206        self.update()
207        self.slicer_mask = self.slicer.update()
208       
209    def onOuterBoxMask(self, event=None):
210        """
211        Call Draw Box Slicer and get mask outside of the box
212        """
213        event.Skip()       
214        #from boxMask import BoxMask
215        if event != None:
216            self.onClearSlicer(event)     
217        self.slicer_z += 1
218        self.slicer =  BoxMask(self, self.subplot,
219                               zorder=self.slicer_z, side=False)
220        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
221        self.subplot.set_xlim(self.data.xmin, self.data.xmax)
222        self.update()
223        self.slicer_mask = self.slicer.update()
224
225    def onInnerSectorMask(self, event=None):
226        """
227        Call Draw Sector Slicer and get mask inside of the sector
228        """
229        event.Skip()
230        from sectorMask import SectorMask
231        if event != None:
232            self.onClearSlicer(event)
233        self.slicer_z += 1
234        self.slicer =  SectorMask(self, self.subplot,
235                                  zorder=self.slicer_z, side=True)
236        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
237        self.subplot.set_xlim(self.data.xmin, self.data.xmax)   
238        self.update()
239        self.slicer_mask = self.slicer.update() 
240
241    def onOuterSectorMask(self,event=None):
242        """
243        Call Draw Sector Slicer and get mask outside of the sector
244        """
245        event.Skip()
246        from sectorMask import SectorMask
247        if event != None:
248            self.onClearSlicer(event)
249        self.slicer_z += 1
250        self.slicer =  SectorMask(self, self.subplot,
251                                  zorder=self.slicer_z, side=False)
252        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
253        self.subplot.set_xlim(self.data.xmin, self.data.xmax)   
254        self.update()     
255        self.slicer_mask = self.slicer.update()   
256
257    def onInnerRingMask(self, event=None):
258        """
259        Perform inner circular cut on Phi and draw circular slicer
260        """
261        event.Skip()
262        from AnnulusSlicer import CircularMask
263        if event != None:
264            self.onClearSlicer(event)
265        self.slicer_z += 1
266        self.slicer = CircularMask(self, self.subplot,
267                                   zorder=self.slicer_z, side=True)
268        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
269        self.subplot.set_xlim(self.data.xmin, self.data.xmax)   
270        self.update()
271        self.slicer_mask = self.slicer.update() 
272
273    def onOuterRingMask(self, event=None):
274        """
275        Perform outer circular cut on Phi and draw circular slicer
276        """
277        event.Skip()
278        from AnnulusSlicer import CircularMask
279        if event != None:
280            self.onClearSlicer(event)
281        self.slicer_z += 1
282        self.slicer = CircularMask(self,self.subplot,
283                                   zorder=self.slicer_z, side=False)   
284        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
285        self.subplot.set_xlim(self.data.xmin, self.data.xmax)
286        self.update()
287        self.slicer_mask = self.slicer.update()     
288       
289    def onAddMask(self, event):
290        """
291        Add new mask to old mask
292        """
293        if not self.slicer == None:
294            data = Data2D()
295            data = self.data
296            self.slicer_mask = self.slicer.update()
297            data.mask = self.data.mask & self.slicer_mask
298            self._check_display_mask(data.mask, event)
299           
300    def _check_display_mask(self, mask, event):
301        """
302        check if the mask valid and update the plot
303       
304        :param mask: mask data
305        """
306        ## Redraw the current image
307        self._update_mask(mask)
308
309    def onEraseMask(self, event):
310        """
311        Erase new mask from old mask
312        """
313        if not self.slicer==None:
314            self.slicer_mask = self.slicer.update()
315            mask = self.data.mask
316            mask[self.slicer_mask==False] = True
317            self._check_display_mask(mask, event)
318           
319    def onResetMask(self, event):
320        """
321        Reset mask to the original mask
322        """       
323        self.slicer_z += 1
324        self.slicer =  BoxMask(self, self.subplot, 
325                               zorder=self.slicer_z, side=True)
326        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
327        self.subplot.set_xlim(self.data.xmin, self.data.xmax)   
328        mask = copy.deepcopy(self.default_mask)
329        self.data.mask = mask
330        # update mask plot
331        self._check_display_mask(mask, event)
332       
333    def onClearMask(self, event):
334        """
335        Clear mask
336        """           
337        self.slicer_z += 1
338        self.slicer =  BoxMask(self, self.subplot,
339                               zorder=self.slicer_z, side=True)
340        self.subplot.set_ylim(self.data.ymin, self.data.ymax)
341        self.subplot.set_xlim(self.data.xmin, self.data.xmax)   
342        #mask = copy.deepcopy(self.default_mask)
343        mask = numpy.ones(len(self.data.mask), dtype=bool)
344        self.data.mask = mask
345        # update mask plot
346        self._check_display_mask(mask, event)
347       
348    def onClearSlicer(self, event):
349        """
350        Clear the slicer on the plot
351        """
352        if not self.slicer == None:
353            self.slicer.clear()
354            self.subplot.figure.canvas.draw()
355            self.slicer = None
356
357    def _setSlicer(self):
358        """
359        Clear the previous slicer and create a new one.Post an internal
360        event.
361       
362        :param slicer: slicer class to create
363        """
364        ## Clear current slicer
365        if not self.slicer == None: 
366            self.slicer.clear()           
367        ## Create a new slicer   
368        self.slicer_z += 1
369        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
370        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
371        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
372        ## Draw slicer
373        self.update()
374        self.slicer.update()
375        msg = "Plotter2D._setSlicer  %s"%self.slicer.__class__.__name__
376        wx.PostEvent(self.parent, StatusEvent(status=msg))
377        # Post slicer event
378        event = self._getEmptySlicerEvent()
379        event.type = self.slicer.__class__.__name__
380        event.obj_class = self.slicer.__class__
381        event.params = self.slicer.get_params()
382        wx.PostEvent(self, event)
383   
384    def update(self, draw=True):
385        """
386        Respond to changes in the model by recalculating the
387        profiles and resetting the widgets.
388        """
389        self.plotpanel.draw()
390       
391    def _set_mask(self, mask):
392        """
393        Set mask
394        """
395        self.data.mask = mask
396       
397    def set_plot_unfocus(self):
398        """
399        Not implemented
400        """
401        pass
402   
403    def _update_mask(self,mask):
404        """
405        Respond to changes in masking
406        """ 
407        # the case of liitle numbers of True points
408        if (len(mask[mask]) < 10 and self.data != None):
409            self.ShowMessage()
410            mask = copy.deepcopy(self.mask)
411            self.data.mask = mask
412        else:
413            self.mask = mask
414        # make temperary data to plot
415        temp_mask = numpy.zeros(len(mask))
416        temp_data = copy.deepcopy(self.data)
417        # temp_data default is None
418        # This method is to distinguish between masked point and data point = 0.
419        temp_mask = temp_mask/temp_mask
420        temp_mask[mask] = temp_data.data[mask]
421        # set temp_data value for self.mask==True, else still None
422        #temp_mask[mask] = temp_data[mask]
423        temp_data.data[mask==False] = temp_mask[mask==False]
424        self.plotpanel.clear()
425        if self.slicer != None:
426            self.slicer.clear()
427            self.slicer = None
428        # Post slicer None event
429        event = self._getEmptySlicerEvent()
430        wx.PostEvent(self, event)
431       
432        ##use this method
433        #set zmax and zmin to plot: Fix it w/ data.
434        if self.plotpanel.scale == 'log':
435            zmax = math.log(max(self.data.data[self.data.data>0]))
436            zmin = math.log(min(self.data.data[self.data.data>0]))
437        else:
438            zmax = max(self.data.data[self.data.data>0])
439            zmin = min(self.data.data[self.data.data>0])
440        #plot   
441        plot = self.plotpanel.image(data=temp_mask,
442                       qx_data=self.data.qx_data,
443                       qy_data=self.data.qy_data,
444                       xmin=self.data.xmin,
445                       xmax=self.data.xmax,
446                       ymin=self.data.ymin,
447                       ymax=self.data.ymax,
448                       zmin=zmin,
449                       zmax=zmax,
450                       cmap=self.cmap,
451                       color=0, symbol=0, label=self.data.name)
452        # axis labels
453        self.plotpanel.axes[0].set_xlabel('$\\rm{Q}_{x}(A^{-1})$')
454        self.plotpanel.axes[0].set_ylabel('$\\rm{Q}_{y}(A^{-1})$')
455        self.plotpanel.render()
456        self.plotpanel.subplot.figure.canvas.draw_idle()
457       
458    def _getEmptySlicerEvent(self):
459        """
460        create an empty slicervent
461        """
462        self.innerbox_rb.SetValue(False)
463        self.outerbox_rb.SetValue(False)
464        self.innersector_rb.SetValue(False)
465        self.outersector_rb.SetValue(False)
466        self.innercircle_rb.SetValue(False)
467        self.outercircle_rb.SetValue(False)
468        return SlicerEvent(type=None,
469                           params=None,
470                           obj_class=None) 
471             
472    def _draw_model(self, event):
473        """
474         on_close, update the model2d plot
475        """
476        pass
477       
478    def freeze_axes(self):
479        """
480        freeze axes
481        """
482        self.plotpanel.axes_frozen = True
483       
484    def thaw_axes(self):
485        """
486        thaw axes
487        """
488        self.plotpanel.axes_frozen = False       
489         
490    def onMouseMotion(self, event):
491        """
492        onMotion event
493        """
494        pass
495   
496    def onWheel(self, event):
497        """
498        on wheel event
499        """
500        pass 
501   
502    def OnClose(self, event):
503        """
504        """
505        try:
506            self.parent._draw_masked_model(event)
507        except:
508            # when called by data panel
509            event.Skip()
510            pass
511       
512class Maskplotpanel(PlotPanel):
513    """
514    """
515    def __init__(self, parent, id=-1, color=None, dpi=None, **kwargs):
516        """
517        """
518        PlotPanel.__init__(self, parent, id=id, color=color, dpi=dpi, **kwargs)
519       
520        # Keep track of the parent Frame
521        self.parent = parent
522        # Internal list of plottable names (because graph
523        # doesn't have a dictionary of handles for the plottables)
524        self.plots = {}
525        self.graph = Graph()
526       
527    def add_toolbar(self):
528        """
529        Add toolbar
530        """
531        # Not implemented
532        pass
533    def on_set_focus(self, event):
534        """
535        send to the parenet the current panel on focus
536        """
537        #change the panel background
538        #self.SetColor((170, 202, 255))
539        self.draw()   
540         
541    def add_image(self, plot):
542        """
543        Add Image
544        """
545        self.plots[plot.name] = plot
546        #init graph
547        self.gaph = Graph()
548        #add plot
549        self.graph.add(plot)
550        #add axes
551        self.graph.xaxis('\\rm{Q}_{x} ', 'A^{-1}')
552        self.graph.yaxis('\\rm{Q}_{y} ', 'A^{-1}')
553        #draw
554        self.graph.render(self)
555        self.subplot.figure.canvas.draw_idle()
556       
557    def onMouseMotion(self, event):
558        """
559        Disable dragging 2D image
560        """
561        pass
562   
563    def onContextMenu(self, event):
564        """
565        Default context menu for a plot panel
566        """
567        # Slicer plot popup menu
568        slicerpop = wx.Menu()
569       
570        id_cm = wx.NewId()
571        slicerpop.Append(id_cm, '&Toggle Linear/Log scale')
572        wx.EVT_MENU(self, id_cm, self._onToggleScale)
573               
574        pos = event.GetPosition()
575        pos = self.ScreenToClient(pos)
576        self.PopupMenu(slicerpop, pos)
577
578class ViewerFrame(wx.Frame):
579    """
580    Add comment
581    """
582    def __init__(self, parent, id, title):
583        """
584        comment
585        :param parent: parent panel/container
586        """
587        # Initialize the Frame object
588        wx.Frame.__init__(self, parent, id, title,
589                          wx.DefaultPosition, wx.Size(950, 850))
590        # Panel for 1D plot
591        self.plotpanel = Maskplotpanel(self, -1, style=wx.RAISED_BORDER)
592
593class ViewApp(wx.App):
594    def OnInit(self):
595        frame = ViewerFrame(None, -1, 'testView')   
596        frame.Show(True)
597        #self.SetTopWindow(frame)
598       
599        return True
600               
601if __name__ == "__main__": 
602    app = ViewApp(0)
603    app.MainLoop()     
Note: See TracBrowser for help on using the repository browser.