source: sasview/guiframe/local_perspectives/plotting/masking.py @ ac8671e

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 ac8671e was db72faf, checked in by Jae Cho <jhjcho@…>, 15 years ago

fixed minor bug

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