source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 7fff5cd

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

minor change

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