source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 0e13148

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

removed Imag2D class

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