source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 16bf519

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 16bf519 was 8b30e62, checked in by Jae Cho <jhjcho@…>, 16 years ago

add id for averaging plot

  • Property mode set to 100644
File size: 18.3 KB
RevLine 
[1bf33c1]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
6See the license text in license.txt
7
8copyright 2008, University of Tennessee
9"""
10
11
12import wx
13import sys
[0d9dae8]14import pylab
15
[1bf33c1]16import danse.common.plottools
17from danse.common.plottools.PlotPanel import PlotPanel
18from danse.common.plottools.plottables import Graph,Data1D
[0d9dae8]19from sans.guicomm.events import EVT_NEW_PLOT,EVT_SLICER_PARS_UPDATE
20from sans.guicomm.events import EVT_SLICER_PARS
21from sans.guicomm.events import StatusEvent ,NewPlotEvent,SlicerEvent
22from sans.guiframe.utils import PanelMenu
[1bf33c1]23from binder import BindArtist
24from Plotter1D import ModelPanel1D
[0d9dae8]25(InternalEvent, EVT_INTERNAL)   = wx.lib.newevent.NewEvent()
26
[1bf33c1]27
[0d9dae8]28
29DEFAULT_QMAX = 0.05
[1bf33c1]30DEFAULT_QSTEP = 0.001
31DEFAULT_BEAM = 0.005
[ef0c170]32BIN_WIDTH = 1.0
[0d9dae8]33
34
35
36
[1bf33c1]37class ModelPanel2D( ModelPanel1D):
38    """
39        Plot panel for use with the GUI manager
40    """
41   
42    ## Internal name for the AUI manager
43    window_name = "plotpanel"
44    ## Title to appear on top of the window
45    window_caption = "Plot Panel"
46    ## Flag to tell the GUI manager that this panel is not
47    #  tied to any perspective
48    ALWAYS_ON = True
49    ## Group ID
50    group_id = None
51   
52   
53    def __init__(self, parent, id = -1,data2d=None, color = None,\
54        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
55        """
56            Initialize the panel
57        """
58        ModelPanel1D.__init__(self, parent, id = id, style = style, **kwargs)
59       
60        ## Reference to the parent window
61        self.parent = parent
62        ## Plottables
63        self.plots = {}
64        self.data2D= data2d
[ac9a5f6]65        self.data =data2d.data
[1bf33c1]66        ## Unique ID (from gui_manager)
67        self.uid = None
68       
69        ## Action IDs for internal call-backs
70        self.action_ids = {}
71        self.connect = BindArtist(self.subplot.figure)
72       
73        # Beam stop
74        self.beamstop_radius = DEFAULT_BEAM
[54cc36a]75       
[f15ed33]76        self.slicer_z = 5
[1bf33c1]77        self.slicer = None
[d468daa]78        #self.parent.Bind(EVT_INTERNAL, self._onEVT_INTERNAL)
79        self.Bind(EVT_INTERNAL, self._onEVT_INTERNAL)
[1bf33c1]80        self.axes_frozen = False
81       
[54cc36a]82        self.panel_slicer=None
83        #self.parent.Bind(EVT_SLICER_PARS, self.onParamChange)
[1bf33c1]84        ## Graph       
85        self.graph = Graph()
86        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
87        self.graph.yaxis("\\rm{Intensity} ","cm^{-1}")
88        self.graph.render(self)
[54cc36a]89        #self.Bind(boxSum.EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PARS)
[0d9dae8]90        self.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
91        self.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
92       
93       
[54cc36a]94    def _onEVT_SLICER_PARS(self, event):
[d468daa]95        #print "paramaters entered on slicer panel", event.type, event.params
[0d9dae8]96        self.slicer.set_params(event.params)
97        from sans.guicomm.events import SlicerPanelEvent
98        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer))
99       
100       
101    def _onEVT_SLICER_PANEL(self, event):
[d468daa]102        #print "box move plotter2D", event.type, event.params
[54cc36a]103        self.panel_slicer.set_slicer(event.type, event.params)
104        from sans.guicomm.events import SlicerPanelEvent
[0d9dae8]105        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer)) 
106       
[1bf33c1]107    def _onEVT_1DREPLOT(self, event):
108        """
109            Data is ready to be displayed
[4b91fd1]110           
111            #TODO: this name should be changed to something more appropriate
112            # Don't forget that changing this name will mean changing code
113            # in plotting.py
114             
[1bf33c1]115            @param event: data event
116        """
[50cbace]117             
118        self.data2D= event.plot
119        self.data =event.plot.data
[1bf33c1]120        #TODO: Check for existence of plot attribute
[ab8f936]121
[1bf33c1]122        # Check whether this is a replot. If we ask for a replot
123        # and the plottable no longer exists, ignore the event.
124        if hasattr(event, "update") and event.update==True \
125            and event.plot.name not in self.plots.keys():
126            return
[ab8f936]127       
[1bf33c1]128        if hasattr(event, "reset"):
129            self._reset()
130        is_new = True
131        if event.plot.name in self.plots.keys():
132            # Check whether the class of plottable changed
133            if not event.plot.__class__==self.plots[event.plot.name].__class__:
[ab8f936]134                #overwrite a plottable using the same name
[1bf33c1]135                self.graph.delete(self.plots[event.plot.name])
136            else:
[ab8f936]137                # plottable is already draw on the panel
[1bf33c1]138                is_new = False
[ab8f936]139           
140        if is_new:
141            # a new plottable overwrites a plotted one  using the same id
142            for plottable in self.plots.itervalues():
[e48a62e]143                if hasattr(event.plot,"id"):
144                    if event.plot.id==plottable.id :
145                        self.graph.delete(plottable)
[ab8f936]146           
147            self.plots[event.plot.name] = event.plot
148            self.graph.add(self.plots[event.plot.name])
149        else:
[4b91fd1]150            # Update the plottable with the new data
151           
152            #TODO: we should have a method to do this,
153            #      something along the lines of:
154            #      plottable1.update_data_from_plottable(plottable2)
155           
156            self.plots[event.plot.name].xmin = event.plot.xmin
157            self.plots[event.plot.name].xmax = event.plot.xmax
158            self.plots[event.plot.name].ymin = event.plot.ymin
159            self.plots[event.plot.name].ymax = event.plot.ymax
160            self.plots[event.plot.name].data = event.plot.data
161            self.plots[event.plot.name].err_data = event.plot.err_data
[ac9a5f6]162            # update qmax with the new xmax of data plotted
163            self.qmax= event.plot.xmax
[4b91fd1]164           
[ac9a5f6]165        self.slicer= None
[ffd23b5]166       
[1bf33c1]167        # Check axis labels
168        #TODO: Should re-factor this
169        #if event.plot._xunit != self.graph.prop["xunit"]:
170        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
[ab8f936]171           
[1bf33c1]172        #if event.plot._yunit != self.graph.prop["yunit"]:
173        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
[0690e1d]174        self.graph.title(self.data2D.name)
[1bf33c1]175        self.graph.render(self)
176        self.subplot.figure.canvas.draw_idle()
177
178
179    def onContextMenu(self, event):
180        """
181            2D plot context menu
182            @param event: wx context event
183        """
184       
185        #slicerpop = wx.Menu()
186        slicerpop = PanelMenu()
187        slicerpop.set_plots(self.plots)
188        slicerpop.set_graph(self.graph)
189   
190        item_list = self.parent.get_context_menu(self.graph)
191        if (not item_list==None) and (not len(item_list)==0):
[d468daa]192               
[1bf33c1]193                for item in item_list:
194                    try:
195                        id = wx.NewId()
196                        slicerpop.Append(id, item[0], item[1])
197                        wx.EVT_MENU(self, id, item[2])
198                    except:
[d468daa]199                        pass
200                        #print sys.exc_value
201                        #print RuntimeError, "View1DPanel2D.onContextMenu: bad menu item"
[1bf33c1]202       
203        slicerpop.AppendSeparator()
204       
205        id = wx.NewId()
206        slicerpop.Append(id, '&Perform circular average')
207        wx.EVT_MENU(self, id, self.onCircular) 
208       
209        id = wx.NewId()
[ef0c170]210        slicerpop.Append(id, '&Sector [Q view]')
[1bf33c1]211        wx.EVT_MENU(self, id, self.onSectorQ) 
212       
213        id = wx.NewId()
[ef0c170]214        slicerpop.Append(id, '&Annulus [Phi view ]')
[1bf33c1]215        wx.EVT_MENU(self, id, self.onSectorPhi) 
[92c2345]216       
[7ab9241]217        id = wx.NewId()
218        slicerpop.Append(id, '&Box Sum')
219        wx.EVT_MENU(self, id, self.onBoxSum) 
220       
[38224f10]221        id = wx.NewId()
[8a7a21b]222        slicerpop.Append(id, '&Box averaging in Qx')
223        wx.EVT_MENU(self, id, self.onBoxavgX) 
224       
225        id = wx.NewId()
226        slicerpop.Append(id, '&Box averaging in Qy')
227        wx.EVT_MENU(self, id, self.onBoxavgY) 
[92c2345]228        if self.slicer !=None:
229            id = wx.NewId()
230            slicerpop.Append(id, '&Clear slicer')
231            wx.EVT_MENU(self, id,  self.onClearSlicer) 
[1bf33c1]232       
[92c2345]233            id = wx.NewId()
234            slicerpop.Append(id, '&Edit Slicer Parameters')
235            wx.EVT_MENU(self, id, self._onEditSlicer) 
[d468daa]236        slicerpop.AppendSeparator() 
237           
[1bf33c1]238        id = wx.NewId()
239        slicerpop.Append(id, '&Save image')
240        wx.EVT_MENU(self, id, self.onSaveImage) 
[d468daa]241       
242        # Option to save the data displayed
243        id = wx.NewId()
244        slicerpop.Append(id,'&Printer setup', 'Set image size')
245        wx.EVT_MENU(self, id, self.onPrinterSetup)
246       
247        id = wx.NewId()
248        slicerpop.Append(id,'&Printer Preview', 'Set image size')
249        wx.EVT_MENU(self, id, self.onPrinterPreview)
250   
251        id = wx.NewId()
252        slicerpop.Append(id,'&Print image', 'Print image ')
253        wx.EVT_MENU(self, id, self.onPrint)
254        slicerpop.AppendSeparator()
[1bf33c1]255        id = wx.NewId()
256        slicerpop.Append(id, '&Toggle Linear/Log scale')
257        wx.EVT_MENU(self, id, self._onToggleScale) 
[d468daa]258                 
259     
[1bf33c1]260        pos = event.GetPosition()
261        pos = self.ScreenToClient(pos)
262        self.PopupMenu(slicerpop, pos)
[aa1b747]263       
[240c805]264   
[1bf33c1]265       
266       
267    def get_corrected_data(self):
268        # Protect against empty data set
269        if self.data == None:
270            return None
271        import copy
272        output = copy.deepcopy(self.data)
273        return output
274    def freeze_axes(self):
275        self.axes_frozen = True
276       
277    def thaw_axes(self):
278        self.axes_frozen = False
279       
280    def onMouseMotion(self,event):
281        pass
282    def onWheel(self, event):
283        pass   
284    def update(self, draw=True):
285        """
286            Respond to changes in the model by recalculating the
287            profiles and resetting the widgets.
288        """
289        #self.slicer.update()
290        self.draw()
291       
292       
293    def _getEmptySlicerEvent(self):
294        return SlicerEvent(type=None,
295                           params=None,
296                           obj_class=None)
297    def _onEVT_INTERNAL(self, event):
298        """
299            I don't understand why Unbind followed by a Bind
300            using a modified self.slicer doesn't work.
301            For now, I post a clear event followed by
302            a new slicer event...
303        """
304        self._setSlicer(event.slicer)
305           
306    def _setSlicer(self, slicer):
307        # Clear current slicer
308        #printEVT("Plotter2D._setSlicer %s" % slicer)
309       
310        if not self.slicer == None: 
311            self.slicer.clear()           
312           
313        self.slicer_z += 1
314        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
[d468daa]315        #print "come here"
[240c805]316        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
317        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
318       
[1bf33c1]319        self.update()
320        self.slicer.update()
321       
322        # Post slicer event
323        event = self._getEmptySlicerEvent()
324        event.type = self.slicer.__class__.__name__
[54cc36a]325       
[1bf33c1]326        event.obj_class = self.slicer.__class__
327        event.params = self.slicer.get_params()
[d468daa]328        #print "Plotter2D: event.type",event.type,event.params, self.parent
[54cc36a]329       
[d468daa]330        #wx.PostEvent(self.parent, event)
331        wx.PostEvent(self, event)
[1bf33c1]332
333    def onCircular(self, event):
334        """
335            perform circular averaging on Data2D
336        """
337       
338        from DataLoader.manipulations import CircularAverage
[ef0c170]339        import math
[216efab]340        self.qmax= max(math.fabs(self.data2D.xmax),math.fabs(self.data2D.xmin))
341        self.ymax=max(math.fabs(self.data2D.ymax),math.fabs(self.data2D.ymin))
342        self.radius= math.sqrt( math.pow(self.qmax,2)+math.pow(self.ymax,2)) 
[d468daa]343        #print "radius?",self.radius
[c73d871]344        # bin_width = self.qmax -self.qmin/nbins
345        #nbins= 30
[8f584c9]346        bin_width = (self.qmax +self.qmax)/100
[c73d871]347       
348        Circle = CircularAverage( r_min=0, r_max=self.radius, bin_width=bin_width)
349       
[1bf33c1]350        circ = Circle(self.data2D)
351        from sans.guiframe.dataFitting import Data1D
352        if hasattr(circ,"dxl"):
353            dxl= circ.dxl
354        else:
355            dxl= None
356        if hasattr(circ,"dxw"):
357            dxw= circ.dxw
358        else:
359            dxw= None
[ef0c170]360       
[1bf33c1]361        new_plot = Data1D(x=circ.x,y=circ.y,dy=circ.dy,dxl=dxl,dxw=dxw)
362        new_plot.name = "Circ avg "+ self.data2D.name
363        new_plot.source=self.data2D.source
[50cbace]364        #new_plot.info=self.data2D.info
[1bf33c1]365        new_plot.interactive = True
366        #print "loader output.detector",output.source
367        new_plot.detector =self.data2D.detector
368       
369        # If the data file does not tell us what the axes are, just assume...
[8f584c9]370        new_plot.xaxis("\\rm{Q}","A^{-1}")
[1bf33c1]371        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
372        new_plot.group_id = "Circ avg "+ self.data2D.name
[8b30e62]373        new_plot.id = "Circ avg "+ self.data2D.name
[1bf33c1]374        self.scale = 'log'
[ef0c170]375       
[1bf33c1]376        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))
[ef0c170]377       
[1bf33c1]378    def _onEditSlicer(self, event):
379        if self.slicer !=None:
380            from SlicerParameters import SlicerParameterPanel
381            dialog = SlicerParameterPanel(self.parent, -1, "Slicer Parameters")
382            dialog.set_slicer(self.slicer.__class__.__name__,
383                            self.slicer.get_params())
384            if dialog.ShowModal() == wx.ID_OK:
385                dialog.Destroy() 
386       
387    def onSectorQ(self, event):
388        """
389            Perform sector averaging on Q
390        """
[d468daa]391        #print "onsector self.data2Dxmax",self.data2D.xmax, self.parent
[ef0c170]392        from SectorSlicer import SectorInteractor
[1bf33c1]393        self.onClearSlicer(event)
[d468daa]394        #wx.PostEvent(self.parent, InternalEvent(slicer= SectorInteractor))
395        wx.PostEvent(self, InternalEvent(slicer= SectorInteractor))
[1bf33c1]396       
397    def onSectorPhi(self, event):
398        """
399            Perform sector averaging on Phi
400        """
[ef0c170]401        from AnnulusSlicer import AnnulusInteractor
[1bf33c1]402        self.onClearSlicer(event)
[d468daa]403        #wx.PostEvent(self.parent, InternalEvent(slicer= AnnulusInteractor))
404        wx.PostEvent(self, InternalEvent(slicer= AnnulusInteractor))
[1bf33c1]405       
[7ab9241]406    def onBoxSum(self,event):
407        from boxSum import BoxSum
408        self.onClearSlicer(event)
[54cc36a]409        #wx.PostEvent(self.parent, InternalEvent(slicer= BoxSum))
410        if not self.slicer == None: 
411            self.slicer.clear()             
412        self.slicer_z += 1
413        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
[d468daa]414        #print "come here"
[54cc36a]415        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
416        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
417       
418        self.update()
419        self.slicer.update()
420       
421        # Post slicer event
422        event = self._getEmptySlicerEvent()
423        event.type = self.slicer.__class__.__name__
424       
425       
426        event.obj_class = self.slicer.__class__
427        event.params = self.slicer.get_params()
[d468daa]428        #print "Plotter2D: event.type",event.type,event.params, self.parent
[8a7a21b]429       
430        from slicerpanel import SlicerPanel
[0d9dae8]431        new_panel = SlicerPanel(parent= self.parent,id= -1,base= self,type=event.type,
[54cc36a]432                                 params=event.params, style=wx.RAISED_BORDER)
433        #new_panel.set_slicer(self.slicer.__class__.__name__,
434        new_panel.window_caption=self.slicer.__class__.__name__+" "+ str(self.data2D.name)
435       
436        self.panel_slicer= new_panel
437       
438        wx.PostEvent(self.panel_slicer, event)
439        from sans.guicomm.events import SlicerPanelEvent
440        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer))
[d468daa]441        #print "finish box sum"
[8a7a21b]442       
443    def onBoxavgX(self,event):
444        from boxSlicer import BoxInteractorX
[38224f10]445        self.onClearSlicer(event)
[d468daa]446        #wx.PostEvent(self.parent, InternalEvent(slicer= BoxInteractorX))
447        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorX))
448       
449       
[8a7a21b]450    def onBoxavgY(self,event):
451        from boxSlicer import BoxInteractorY
452        self.onClearSlicer(event)
[d468daa]453        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorY))
454        #wx.PostEvent(self.parent, InternalEvent(slicer= BoxInteractorY))
[b319def8]455       
[1bf33c1]456    def onClearSlicer(self, event):
457        """
458            Clear the slicer on the plot
459        """
460        if not self.slicer==None:
461            self.slicer.clear()
462            self.subplot.figure.canvas.draw()
463            self.slicer = None
464       
465            # Post slicer None event
466            event = self._getEmptySlicerEvent()
[d468daa]467            #wx.PostEvent(self.parent, event)
468            wx.PostEvent(self, event)
[1bf33c1]469         
470    def _onEditDetector(self, event):
471        print "on parameter"
472       
473       
474    def _onToggleScale(self, event):
475        """
476            toggle pixel scale and replot image
477        """
478        if self.scale == 'log':
479            self.scale = 'linear'
480        else:
481            self.scale = 'log'
482        self.image(self.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
483                   self.ymax_2D,self.zmin_2D ,self.zmax_2D )
484        wx.PostEvent(self.parent, StatusEvent(status="Image is in %s scale"%self.scale))
485       
[0f6d05f8]486        """     
487            #TODO: this name should be changed to something more appropriate
488            # Don't forget that changing this name will mean changing code
489            # in plotting.py
490             
491            # Update the plottable with the new data
492           
493            #TODO: we should have a method to do this,
494            #      something along the lines of:
495            #      plottable1.update_data_from_plottable(plottable2)
496           
497            self.plots[event.plot.name].xmin = event.plot.xmin
498            self.plots[event.plot.name].xmax = event.plot.xmax
499            self.plots[event.plot.name].ymin = event.plot.ymin
500            self.plots[event.plot.name].ymax = event.plot.ymax
501            self.plots[event.plot.name].data = event.plot.data
502            self.plots[event.plot.name].err_data = event.plot.err_data
503        """
Note: See TracBrowser for help on using the repository browser.