source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ bb4f50e

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

Added qx qy data as 2D input parameters

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