source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 75bbee7f

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 75bbee7f was 8dfdd20, checked in by Gervaise Alina <gervyh@…>, 15 years ago

change detector dialog to allow cmap selection

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