source: sasview/guiframe/local_perspectives/plotting/masking.py @ 52f3c98

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 52f3c98 was 21969a4a, checked in by Jae Cho <jhjcho@…>, 14 years ago

moving fixes from branch

  • Property mode set to 100644
File size: 21.1 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_STATICBOX_WIDTH = 400
37_SCALE = 1e-6
38
39#SLD panel size
40if sys.platform.count("win32") > 0:
41    _STATICBOX_WIDTH = 380
42    PANEL_SIZE = 420
43    FONT_VARIANT = 0
44else:
45    _STATICBOX_WIDTH = 410
46    PANEL_SIZE = 450
47    FONT_VARIANT = 1
48   
49
50class MaskPanel(wx.Dialog):
51    """
52    Provides the Mask Editor GUI.
53    """
54    ## Internal nickname for the window, used by the AUI manager
55    window_name = "Mask Editor"
56    ## Name to appear on the window title bar
57    window_caption = "Mask Editor"
58    ## Flag to tell the AUI manager to put this panel in the center pane
59    CENTER_PANE = True
60    def __init__(self, parent=None, base=None, 
61                 data=None, id=-1, *args, **kwds):
62        kwds["style"] = wx.DEFAULT_DIALOG_STYLE
63        kwds["size"] = wx.Size(_STATICBOX_WIDTH * 2, PANEL_SIZE) 
64        wx.Dialog.__init__(self, parent, id=id,  *args, **kwds)
65       
66        if data != None:
67            #Font size
68            kwds = []
69            self.SetWindowVariant(variant=FONT_VARIANT)
70            self.SetTitle("Mask Editor for " + data.name)
71            self.parent = base
72            self.data = data
73            self.str = self.data.__str__()
74            ## mask for 2D
75            self.mask = data.mask
76            self.default_mask = copy.deepcopy(data.mask)
77            ## masked data from GUI
78            self.slicer_mask = None
79            self.slicer = None
80            self.slicer_z = 5
81            self.data.interactive = True
82            ## when 2 data have the same id override the 1 st plotted
83            self.name = self.data.name
84            # Panel for 2D plot
85            self.plotpanel = Maskplotpanel(self, -1,
86                                           style=wx.TRANSPARENT_WINDOW)
87            self.cmap = DEFAULT_CMAP
88            ## Create Artist and bind it
89            self.subplot = self.plotpanel.subplot
90            self.connect = BindArtist(self.subplot.figure)
91            self._setup_layout()
92            self.newplot = Data2D(image=self.data.data)
93            self.newplot.setValues(self.data)
94            self.plotpanel.add_image(self.newplot) 
95            self._update_mask(self.mask)
96            self.Centre()
97            self.Layout()
98            # bind evt_close to _draw in fitpage
99            self.Bind(wx.EVT_CLOSE, self._draw_model)
100           
101    def ShowMessage(self, msg=''):
102        """
103        Show error message when mask covers whole data area
104        """
105        mssg = 'Erase, redraw or clear the mask. \n\r'
106        mssg += 'The data range can not be completely masked... \n\r'
107        mssg += msg
108        wx.MessageBox(mssg, 'Error', wx.OK | wx.ICON_ERROR)
109   
110    def _setup_layout(self):
111        """
112        Set up the layout
113        """
114        shape = "Select a Shape for Masking:"
115        #  panel
116        sizer = wx.GridBagSizer(10, 10)
117        #---------inputs----------------
118        shape_txt = wx.StaticText(self, -1, shape) 
119        sizer.Add(shape_txt, (1, 1), flag=wx.TOP|wx.LEFT|wx.BOTTOM, border=5)
120        self.innersector_rb = wx.RadioButton(self, -1, "Double Wings")
121        self.Bind(wx.EVT_RADIOBUTTON, self.onInnerSectorMask,
122                  id=self.innersector_rb.GetId())
123        sizer.Add(self.innersector_rb, (2, 1), 
124                  flag=wx.RIGHT|wx.BOTTOM, border=5)
125        self.innercircle_rb = wx.RadioButton(self, -1, "Circular Disk")
126        self.Bind(wx.EVT_RADIOBUTTON, self.onInnerRingMask,
127                  id=self.innercircle_rb.GetId())
128        sizer.Add(self.innercircle_rb, (3, 1),
129                   flag=wx.RIGHT|wx.BOTTOM, border=5)
130       
131        self.innerbox_rb = wx.RadioButton(self, -1, "Rectangular Disk")
132        self.Bind(wx.EVT_RADIOBUTTON, self.onInnerBoxMask,
133                  id=self.innerbox_rb.GetId())
134        sizer.Add(self.innerbox_rb, (4, 1), flag=wx.RIGHT|wx.BOTTOM, border=5)
135
136        self.outersector_rb = wx.RadioButton(self, -1, "Double Wing Window")
137        self.Bind(wx.EVT_RADIOBUTTON, self.onOuterSectorMask, 
138                  id=self.outersector_rb.GetId())
139        sizer.Add(self.outersector_rb, (5, 1),
140                  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,
145                  id=self.outercircle_rb.GetId())
146        sizer.Add(self.outercircle_rb, (6, 1), 
147                  flag=wx.RIGHT|wx.BOTTOM, border=5)
148        #outerbox_txt = wx.StaticText(self, -1, 'Outer Box')
149        self.outerbox_rb = wx.RadioButton(self, -1, "Rectangular Window")
150        self.Bind(wx.EVT_RADIOBUTTON, self.onOuterBoxMask, 
151                  id=self.outerbox_rb.GetId())
152        sizer.Add(self.outerbox_rb, (7, 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 _update_mask(self,mask):
398        """
399        Respond to changes in masking
400        """ 
401        # the case of liitle numbers of True points
402        if (len(mask[mask]) < 10 and self.data != None):
403            self.ShowMessage()
404            mask = copy.deepcopy(self.mask)
405            self.data.mask = mask
406        else:
407            self.mask = mask
408        # make temperary data to plot
409        temp_mask = numpy.zeros(len(mask))
410        temp_data = copy.deepcopy(self.data)
411        # temp_data default is None
412        # This method is to distinguish between masked point and data point = 0.
413        temp_mask = temp_mask/temp_mask
414        temp_mask[mask] = temp_data.data[mask]
415        # set temp_data value for self.mask==True, else still None
416        #temp_mask[mask] = temp_data[mask]
417        temp_data.data[mask==False] = temp_mask[mask==False]
418        self.plotpanel.clear()
419        if self.slicer != None:
420            self.slicer.clear()
421            self.slicer = None
422        # Post slicer None event
423        event = self._getEmptySlicerEvent()
424        wx.PostEvent(self, event)
425       
426        ##use this method
427        #set zmax and zmin to plot: Fix it w/ data.
428        if self.plotpanel.scale == 'log':
429            zmax = math.log(max(self.data.data[self.data.data>0]))
430            zmin = math.log(min(self.data.data[self.data.data>0]))
431        else:
432            zmax = max(self.data.data[self.data.data>0])
433            zmin = min(self.data.data[self.data.data>0])
434        #plot   
435        plot = self.plotpanel.image(data=temp_mask,
436                       qx_data=self.data.qx_data,
437                       qy_data=self.data.qy_data,
438                       xmin=self.data.xmin,
439                       xmax=self.data.xmax,
440                       ymin=self.data.ymin,
441                       ymax=self.data.ymax,
442                       zmin=zmin,
443                       zmax=zmax,
444                       cmap=self.cmap,
445                       color=0, symbol=0, label=self.data.name)
446        # axis labels
447        self.plotpanel.axes[0].set_xlabel('$\\rm{Q}_{x}(A^{-1})$')
448        self.plotpanel.axes[0].set_ylabel('$\\rm{Q}_{y}(A^{-1})$')
449        self.plotpanel.render()
450        self.plotpanel.subplot.figure.canvas.draw_idle()
451       
452    def _getEmptySlicerEvent(self):
453        """
454        create an empty slicervent
455        """
456        self.innerbox_rb.SetValue(False)
457        self.outerbox_rb.SetValue(False)
458        self.innersector_rb.SetValue(False)
459        self.outersector_rb.SetValue(False)
460        self.innercircle_rb.SetValue(False)
461        self.outercircle_rb.SetValue(False)
462        return SlicerEvent(type=None,
463                           params=None,
464                           obj_class=None) 
465             
466    def _draw_model(self, event):
467        """
468         on_close, update the model2d plot
469        """
470        pass
471       
472    def freeze_axes(self):
473        """
474        freeze axes
475        """
476        self.plotpanel.axes_frozen = True
477       
478    def thaw_axes(self):
479        """
480        thaw axes
481        """
482        self.plotpanel.axes_frozen = False       
483         
484    def onMouseMotion(self, event):
485        """
486        onMotion event
487        """
488        pass
489   
490    def onWheel(self, event):
491        """
492        on wheel event
493        """
494        pass 
495           
496class Maskplotpanel(PlotPanel):
497    """
498    """
499    def __init__(self, parent, id=-1, color=None, dpi=None, **kwargs):
500        """
501        """
502        PlotPanel.__init__(self, parent, id=id, color=color, dpi=dpi, **kwargs)
503       
504        # Keep track of the parent Frame
505        self.parent = parent
506        # Internal list of plottable names (because graph
507        # doesn't have a dictionary of handles for the plottables)
508        self.plots = {}
509        self.graph = Graph()
510       
511    def add_toolbar(self):
512        """
513        Add toolbar
514        """
515        # Not implemented
516        pass
517    def on_set_focus(self, event):
518        """
519        send to the parenet the current panel on focus
520        """
521        #change the panel background
522        #self.SetColor((170, 202, 255))
523        self.draw()   
524         
525    def add_image(self, plot):
526        """
527        Add Image
528        """
529        self.plots[plot.name] = plot
530        #init graph
531        self.gaph = Graph()
532        #add plot
533        self.graph.add(plot)
534        #add axes
535        self.graph.xaxis('\\rm{Q}_{x} ', 'A^{-1}')
536        self.graph.yaxis('\\rm{Q}_{y} ', 'A^{-1}')
537        #draw
538        self.graph.render(self)
539        self.subplot.figure.canvas.draw_idle()
540       
541    def onMouseMotion(self, event):
542        """
543        Disable dragging 2D image
544        """
545        pass
546   
547    def onContextMenu(self, event):
548        """
549        Default context menu for a plot panel
550        """
551        # Slicer plot popup menu
552        slicerpop = wx.Menu()
553       
554        id_cm = wx.NewId()
555        slicerpop.Append(id_cm, '&Toggle Linear/Log scale')
556        wx.EVT_MENU(self, id_cm, self._onToggleScale)
557               
558        pos = event.GetPosition()
559        pos = self.ScreenToClient(pos)
560        self.PopupMenu(slicerpop, pos)
561
562class ViewerFrame(wx.Frame):
563    """
564    Add comment
565    """
566    def __init__(self, parent, id, title):
567        """
568        comment
569        :param parent: parent panel/container
570        """
571        # Initialize the Frame object
572        wx.Frame.__init__(self, parent, id, title,
573                          wx.DefaultPosition, wx.Size(950, 850))
574        # Panel for 1D plot
575        self.plotpanel = Maskplotpanel(self, -1, style=wx.RAISED_BORDER)
576
577class ViewApp(wx.App):
578    def OnInit(self):
579        frame = ViewerFrame(None, -1, 'testView')   
580        frame.Show(True)
581        self.SetTopWindow(frame)
582       
583        return True
584               
585if __name__ == "__main__": 
586    app = ViewApp(0)
587    app.MainLoop()     
Note: See TracBrowser for help on using the repository browser.