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

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 fcc5680 was 55a0dc1, checked in by Gervaise Alina <gervyh@…>, 14 years ago

remove reference to guicomm in guiframe

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