source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 76c1727

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 76c1727 was 0f3fefe, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

guiframe: fix 2D scale change

  • Property mode set to 100644
File size: 19.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
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].err_data = event.plot.err_data
151            # update qmax with the new xmax of data plotted
152            self.qmax= event.plot.xmax
153           
154        self.slicer= None
155        # Check axis labels
156        #TODO: Should re-factor this
157        ## render the graph with its new content
158               
159        #data2D: put 'Pixel (Number)' for axis title and unit in case of having no detector info and none in _units
160        if len(self.data2D.detector) < 1: 
161            if len(event.plot._xunit)< 1 and len(event.plot._yunit) < 1:
162                event.plot._xaxis = '\\rm{x}'
163                event.plot._yaxis = '\\rm{y}'
164                event.plot._xunit = 'pixel'
165                event.plot._yunit = 'pixel'
166
167        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
168        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
169        self.graph.title(self.data2D.name)
170        self.graph.render(self)
171        self.subplot.figure.canvas.draw_idle()
172        ## store default value of zmin and zmax
173        self.default_zmin_ctl = self.zmin_2D
174        self.default_zmax_ctl = self.zmax_2D
175
176
177    def onContextMenu(self, event):
178        """
179            2D plot context menu
180            @param event: wx context event
181        """
182       
183        slicerpop = PanelMenu()
184        slicerpop.set_plots(self.plots)
185        slicerpop.set_graph(self.graph)
186             
187        id = wx.NewId()
188        slicerpop.Append(id, '&Save image')
189        wx.EVT_MENU(self, id, self.onSaveImage)
190       
191        id = wx.NewId()
192        slicerpop.Append(id,'&Print image', 'Print image ')
193        wx.EVT_MENU(self, id, self.onPrint)
194       
195        id = wx.NewId()
196        slicerpop.Append(id,'&Print Preview', 'image preview for print')
197        wx.EVT_MENU(self, id, self.onPrinterPreview)
198       
199        slicerpop.AppendSeparator()
200        if len(self.data2D.detector) == 1:       
201           
202            item_list = self.parent.get_context_menu(self.graph)
203            if (not item_list==None) and (not len(item_list)==0):
204                   
205                    for item in item_list:
206                        try:
207                            id = wx.NewId()
208                            slicerpop.Append(id, item[0], item[1])
209                            wx.EVT_MENU(self, id, item[2])
210                        except:
211                            wx.PostEvent(self.parent, StatusEvent(status=\
212                            "ModelPanel1D.onContextMenu: bad menu item  %s"%sys.exc_value))
213                            pass
214                    slicerpop.AppendSeparator()
215           
216            id = wx.NewId()
217            slicerpop.Append(id, '&Perform circular average')
218            wx.EVT_MENU(self, id, self.onCircular) 
219           
220            id = wx.NewId()
221            slicerpop.Append(id, '&Sector [Q view]')
222            wx.EVT_MENU(self, id, self.onSectorQ) 
223           
224            id = wx.NewId()
225            slicerpop.Append(id, '&Annulus [Phi view ]')
226            wx.EVT_MENU(self, id, self.onSectorPhi) 
227           
228            id = wx.NewId()
229            slicerpop.Append(id, '&Box Sum')
230            wx.EVT_MENU(self, id, self.onBoxSum) 
231           
232            id = wx.NewId()
233            slicerpop.Append(id, '&Box averaging in Qx')
234            wx.EVT_MENU(self, id, self.onBoxavgX) 
235           
236            id = wx.NewId()
237            slicerpop.Append(id, '&Box averaging in Qy')
238            wx.EVT_MENU(self, id, self.onBoxavgY) 
239           
240            if self.slicer !=None :
241                id = wx.NewId()
242                slicerpop.Append(id, '&Clear slicer')
243                wx.EVT_MENU(self, id,  self.onClearSlicer) 
244               
245                if self.slicer.__class__.__name__ !="BoxSum":
246                    id = wx.NewId()
247                    slicerpop.Append(id, '&Edit Slicer Parameters')
248                    wx.EVT_MENU(self, id, self._onEditSlicer) 
249                   
250            slicerpop.AppendSeparator() 
251           
252        id = wx.NewId()
253        slicerpop.Append(id, '&Detector Parameters')
254        wx.EVT_MENU(self, id, self._onEditDetector) 
255       
256       
257        id = wx.NewId()
258        slicerpop.Append(id, '&Toggle Linear/Log scale')
259        wx.EVT_MENU(self, id, self._onToggleScale) 
260                 
261        pos = event.GetPosition()
262        pos = self.ScreenToClient(pos)
263        self.PopupMenu(slicerpop, pos)
264   
265   
266    def _onEditDetector(self, event):
267        """
268            Allow to view and edits  detector parameters
269            @param event: wx.menu event
270        """
271       
272        import detector_dialog
273       
274        dialog = detector_dialog.DetectorDialog(self, -1,base=self.parent,
275                       reset_zmin_ctl =self.default_zmin_ctl,
276                       reset_zmax_ctl = self.default_zmax_ctl,cmap=self.cmap)
277        ## info of current detector and data2D
278        xnpts = len(self.data2D.x_bins)
279        ynpts = len(self.data2D.y_bins)
280        xmax = max(self.data2D.xmin, self.data2D.xmax)
281        ymax = max(self.data2D.ymin, self.data2D.ymax)
282        qmax = math.sqrt(math.pow(xmax,2)+math.pow(ymax,2))
283        beam = self.data2D.xmin
284     
285        ## set dialog window content
286        dialog.setContent(xnpts=xnpts,ynpts=ynpts,qmax=qmax,
287                           beam=self.data2D.xmin,
288                           zmin = self.zmin_2D,
289                          zmax = self.zmax_2D)
290        if dialog.ShowModal() == wx.ID_OK:
291            evt = dialog.getContent()
292            self.zmin_2D = evt.zmin
293            self.zmax_2D = evt.zmax
294            self.cmap= evt.cmap
295       
296        dialog.Destroy()
297        ## Redraw the current image
298        self.image(data= self.data2D.data,
299                   xmin= self.data2D.xmin,
300                   xmax= self.data2D.xmax,
301                   ymin= self.data2D.ymin,
302                   ymax= self.data2D.ymax,
303                   zmin= self.zmin_2D,
304                   zmax= self.zmax_2D,
305                   cmap= self.cmap,
306                   color=0,symbol=0,label=self.data2D.name)#'data2D')
307        self.subplot.figure.canvas.draw_idle()
308       
309       
310 
311    def freeze_axes(self):
312        self.axes_frozen = True
313       
314    def thaw_axes(self):
315        self.axes_frozen = False
316       
317    def onMouseMotion(self,event):
318        pass
319    def onWheel(self, event):
320        pass 
321     
322    def update(self, draw=True):
323        """
324            Respond to changes in the model by recalculating the
325            profiles and resetting the widgets.
326        """
327        self.draw()
328       
329       
330    def _getEmptySlicerEvent(self):
331        """
332            create an empty slicervent
333        """
334        return SlicerEvent(type=None,
335                           params=None,
336                           obj_class=None)
337    def _onEVT_INTERNAL(self, event):
338        """
339            Draw the slicer
340            @param event: wx.lib.newevent (SlicerEvent) containing slicer
341            parameter
342        """
343        self._setSlicer(event.slicer)
344           
345    def _setSlicer(self, slicer):
346        """
347            Clear the previous slicer and create a new one.Post an internal
348            event.
349            @param slicer: slicer class to create
350        """
351       
352        ## Clear current slicer
353        if not self.slicer == None: 
354            self.slicer.clear()           
355        ## Create a new slicer   
356        self.slicer_z += 1
357        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
358        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
359        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
360        ## Draw slicer
361        self.update()
362        self.slicer.update()
363        wx.PostEvent(self.parent, StatusEvent(status=\
364                        "Plotter2D._setSlicer  %s"%self.slicer.__class__.__name__))
365        # Post slicer event
366        event = self._getEmptySlicerEvent()
367        event.type = self.slicer.__class__.__name__
368       
369        event.obj_class = self.slicer.__class__
370        event.params = self.slicer.get_params()
371        wx.PostEvent(self, event)
372
373
374    def onCircular(self, event):
375        """
376            perform circular averaging on Data2D
377            @param event: wx.menu event
378        """
379       
380        from DataLoader.manipulations import CircularAverage
381        ## compute the maximum radius of data2D
382        self.qmax= max(math.fabs(self.data2D.xmax),math.fabs(self.data2D.xmin))
383        self.ymax=max(math.fabs(self.data2D.ymax),math.fabs(self.data2D.ymin))
384        self.radius= math.sqrt( math.pow(self.qmax,2)+math.pow(self.ymax,2)) 
385        ##Compute beam width
386        bin_width = (self.qmax +self.qmax)/100
387        ## Create data1D circular average of data2D
388        Circle = CircularAverage( r_min=0, r_max=self.radius, bin_width=bin_width)
389        circ = Circle(self.data2D)
390       
391        from sans.guiframe.dataFitting import Data1D
392        if hasattr(circ,"dxl"):
393            dxl= circ.dxl
394        else:
395            dxl= None
396        if hasattr(circ,"dxw"):
397            dxw= circ.dxw
398        else:
399            dxw= None
400       
401        new_plot = Data1D(x=circ.x,y=circ.y,dy=circ.dy)
402        new_plot.dxl=dxl
403        new_plot.dxw=dxw
404        new_plot.name = "Circ avg "+ self.data2D.name
405        new_plot.source=self.data2D.source
406        #new_plot.info=self.data2D.info
407        new_plot.interactive = True
408        new_plot.detector =self.data2D.detector
409        ## If the data file does not tell us what the axes are, just assume...
410        new_plot.xaxis("\\rm{Q}","A^{-1}")
411        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
412        new_plot.group_id = "Circ avg "+ self.data2D.name
413        new_plot.id = "Circ avg "+ self.data2D.name
414        new_plot.is_data= True
415       
416        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))
417       
418       
419    def _onEditSlicer(self, event):
420        """
421            Is available only when a slicer is drawn.Create a dialog
422            window where the user can enter value to reset slicer
423            parameters.
424            @param event: wx.menu event
425        """
426        if self.slicer !=None:
427            from SlicerParameters import SlicerParameterPanel
428            dialog = SlicerParameterPanel(self, -1, "Slicer Parameters")
429            dialog.set_slicer(self.slicer.__class__.__name__,
430                            self.slicer.get_params())
431            if dialog.ShowModal() == wx.ID_OK:
432                dialog.Destroy() 
433       
434       
435    def onSectorQ(self, event):
436        """
437            Perform sector averaging on Q and draw sector slicer
438        """
439        from SectorSlicer import SectorInteractor
440        self.onClearSlicer(event)
441        wx.PostEvent(self, InternalEvent(slicer= SectorInteractor))
442       
443    def onSectorPhi(self, event):
444        """
445            Perform sector averaging on Phi and draw annulus slicer
446        """
447        from AnnulusSlicer import AnnulusInteractor
448        self.onClearSlicer(event)
449        wx.PostEvent(self, InternalEvent(slicer= AnnulusInteractor))
450       
451    def onBoxSum(self,event):
452        from boxSum import BoxSum
453        self.onClearSlicer(event)
454                   
455        self.slicer_z += 1
456        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
457       
458        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
459        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
460       
461        self.update()
462        self.slicer.update()
463        ## Value used to initially set the slicer panel
464        type = self.slicer.__class__.__name__
465        params = self.slicer.get_params()
466        ## Create a new panel to display results of summation of Data2D
467        from slicerpanel import SlicerPanel
468        new_panel = SlicerPanel(parent= self.parent, id= -1,
469                                    base= self, type= type,
470                                    params= params, style= wx.RAISED_BORDER)
471       
472        new_panel.window_caption=self.slicer.__class__.__name__+" "+ str(self.data2D.name)
473        new_panel.window_name = self.slicer.__class__.__name__+" "+ str(self.data2D.name)
474        ## Store a reference of the new created panel
475        self.panel_slicer= new_panel
476        ## save the window_caption of the new panel in the current slicer
477        self.slicer.set_panel_name( name= new_panel.window_caption)
478        ## post slicer panel to guiframe to display it
479        from sans.guicomm.events import SlicerPanelEvent
480        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer,
481                                                    main_panel =self))
482
483       
484    def onBoxavgX(self,event):
485        """
486            Perform 2D data averaging on Qx
487            Create a new slicer .
488            @param event: wx.menu event
489        """
490        from boxSlicer import BoxInteractorX
491        self.onClearSlicer(event)
492        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorX))
493       
494       
495    def onBoxavgY(self,event):
496        """
497            Perform 2D data averaging on Qy
498            Create a new slicer .
499            @param event: wx.menu event
500        """
501        from boxSlicer import BoxInteractorY
502        self.onClearSlicer(event)
503        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorY))
504       
505       
506    def onClearSlicer(self, event):
507        """
508            Clear the slicer on the plot
509        """
510        if not self.slicer==None:
511            self.slicer.clear()
512            self.subplot.figure.canvas.draw()
513            self.slicer = None
514       
515            # Post slicer None event
516            event = self._getEmptySlicerEvent()
517            wx.PostEvent(self, event)
518         
519   
520    def _onToggleScale(self, event):
521        """
522            toggle scale and replot image
523        """
524        if self.scale == 'log':
525            self.scale = 'linear'
526        else:
527            self.scale = 'log'
528        self.image(self.data2D.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
529                   self.ymax_2D,self.zmin_2D ,self.zmax_2D )
530       
531        self.subplot.figure.canvas.draw_idle()
Note: See TracBrowser for help on using the repository browser.