source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 15550f4

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

working on tiff reader and plotter

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