source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 588f84f

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 588f84f was 1debb29, checked in by Gervaise Alina <gervyh@…>, 16 years ago

move code of panel slicer from guiframe to fitting plugin

  • Property mode set to 100644
File size: 19.7 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 = '\\rm{x}'
158                event.plot._yaxis = '\\rm{y}'
159                event.plot._xunit = 'pixel'
160                event.plot._yunit = 'pixel'
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        new_plot.is_data= True
401       
402        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))
403       
404       
405    def _onEditSlicer(self, event):
406        """
407            Is available only when a slicer is drawn.Create a dialog
408            window where the user can enter value to reset slicer
409            parameters.
410            @param event: wx.menu event
411        """
412        if self.slicer !=None:
413            from SlicerParameters import SlicerParameterPanel
414            dialog = SlicerParameterPanel(self, -1, "Slicer Parameters")
415            dialog.set_slicer(self.slicer.__class__.__name__,
416                            self.slicer.get_params())
417            if dialog.ShowModal() == wx.ID_OK:
418                dialog.Destroy() 
419       
420       
421    def onSectorQ(self, event):
422        """
423            Perform sector averaging on Q and draw sector slicer
424        """
425        from SectorSlicer import SectorInteractor
426        self.onClearSlicer(event)
427        wx.PostEvent(self, InternalEvent(slicer= SectorInteractor))
428       
429    def onSectorPhi(self, event):
430        """
431            Perform sector averaging on Phi and draw annulus slicer
432        """
433        from AnnulusSlicer import AnnulusInteractor
434        self.onClearSlicer(event)
435        wx.PostEvent(self, InternalEvent(slicer= AnnulusInteractor))
436       
437    def onBoxSum(self,event):
438        from boxSum import BoxSum
439        self.onClearSlicer(event)
440                   
441        self.slicer_z += 1
442        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
443       
444        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
445        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
446       
447        self.update()
448        self.slicer.update()
449        ## Value used to initially set the slicer panel
450        type = self.slicer.__class__.__name__
451        params = self.slicer.get_params()
452        ## Create a new panel to display results of summation of Data2D
453        from slicerpanel import SlicerPanel
454        new_panel = SlicerPanel(parent= self.parent, id= -1,
455                                    base= self, type= type,
456                                    params= params, style= wx.RAISED_BORDER)
457       
458        new_panel.window_caption=self.slicer.__class__.__name__+" "+ str(self.data2D.name)
459        new_panel.window_name = self.slicer.__class__.__name__+" "+ str(self.data2D.name)
460        ## Store a reference of the new created panel
461        self.panel_slicer= new_panel
462        ## save the window_caption of the new panel in the current slicer
463        self.slicer.set_panel_name( name= new_panel.window_caption)
464        ## post slicer panel to guiframe to display it
465        from sans.guicomm.events import SlicerPanelEvent
466        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer,
467                                                    main_panel =self))
468
469       
470    def onBoxavgX(self,event):
471        """
472            Perform 2D data averaging on Qx
473            Create a new slicer .
474            @param event: wx.menu event
475        """
476        from boxSlicer import BoxInteractorX
477        self.onClearSlicer(event)
478        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorX))
479       
480       
481    def onBoxavgY(self,event):
482        """
483            Perform 2D data averaging on Qy
484            Create a new slicer .
485            @param event: wx.menu event
486        """
487        from boxSlicer import BoxInteractorY
488        self.onClearSlicer(event)
489        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorY))
490       
491       
492    def onClearSlicer(self, event):
493        """
494            Clear the slicer on the plot
495        """
496        if not self.slicer==None:
497            self.slicer.clear()
498            self.subplot.figure.canvas.draw()
499            self.slicer = None
500       
501            # Post slicer None event
502            event = self._getEmptySlicerEvent()
503            wx.PostEvent(self, event)
504         
505   
506    def _onToggleScale(self, event):
507        """
508            toggle pixel scale and replot image
509        """
510        if self.scale == 'log':
511            self.scale = 'linear'
512        else:
513            self.scale = 'log'
514        self.image(self.data2D.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
515                   self.ymax_2D,self.zmin_2D ,self.zmax_2D )
516        wx.PostEvent(self.parent, StatusEvent(status="Image is in %s scale"%self.scale))
517       
518        """     
519            #TODO: this name should be changed to something more appropriate
520            # Don't forget that changing this name will mean changing code
521            # in plotting.py
522             
523            # Update the plottable with the new data
524           
525            #TODO: we should have a method to do this,
526            #      something along the lines of:
527            #      plottable1.update_data_from_plottable(plottable2)
528           
529            self.plots[event.plot.name].xmin = event.plot.xmin
530            self.plots[event.plot.name].xmax = event.plot.xmax
531            self.plots[event.plot.name].ymin = event.plot.ymin
532            self.plots[event.plot.name].ymax = event.plot.ymax
533            self.plots[event.plot.name].data = event.plot.data
534            self.plots[event.plot.name].err_data = event.plot.err_data
535        """
Note: See TracBrowser for help on using the repository browser.