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

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

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