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
RevLine 
[1bf33c1]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
[ea290ee]13import sys, math
[0d9dae8]14import pylab
15
[1bf33c1]16import danse.common.plottools
17from danse.common.plottools.PlotPanel import PlotPanel
18from danse.common.plottools.plottables import Graph,Data1D
[0bd2cd8]19from sans.guicomm.events import EVT_NEW_PLOT
[0d9dae8]20from sans.guicomm.events import EVT_SLICER_PARS
21from sans.guicomm.events import StatusEvent ,NewPlotEvent,SlicerEvent
22from sans.guiframe.utils import PanelMenu
[1bf33c1]23from binder import BindArtist
24from Plotter1D import ModelPanel1D
[0d9dae8]25(InternalEvent, EVT_INTERNAL)   = wx.lib.newevent.NewEvent()
26
[1bf33c1]27
[0d9dae8]28
29DEFAULT_QMAX = 0.05
[1bf33c1]30DEFAULT_QSTEP = 0.001
31DEFAULT_BEAM = 0.005
[ef0c170]32BIN_WIDTH = 1.0
[0d9dae8]33
34
35
36
[1bf33c1]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
[6c0568b]62        ## Dictionary containing Plottables
[1bf33c1]63        self.plots = {}
[6c0568b]64        ## Save reference of the current plotted
65        self.data2D = data2d
[1bf33c1]66        ## Unique ID (from gui_manager)
67        self.uid = None
68        ## Action IDs for internal call-backs
69        self.action_ids = {}
[6c0568b]70        ## Create Artist and bind it
[1bf33c1]71        self.connect = BindArtist(self.subplot.figure)
[6c0568b]72        ## Beam stop
[1bf33c1]73        self.beamstop_radius = DEFAULT_BEAM
[6c0568b]74        ## to set the order of lines drawn first.
[f15ed33]75        self.slicer_z = 5
[6c0568b]76        ## Reference to the current slicer
[1bf33c1]77        self.slicer = None
[6c0568b]78        ## event to send slicer info
[d468daa]79        self.Bind(EVT_INTERNAL, self._onEVT_INTERNAL)
[1bf33c1]80       
[6c0568b]81        self.axes_frozen = False
82        ## panel that contains result from slicer motion (ex: Boxsum info)
[54cc36a]83        self.panel_slicer=None
[8dfdd20]84       
[1bf33c1]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)
[8dfdd20]90        ## store default value of zmin and zmax
91        self.default_zmin_ctl = self.zmin_2D
92        self.default_zmax_ctl = self.zmax_2D
[0d9dae8]93       
[1bf33c1]94    def _onEVT_1DREPLOT(self, event):
95        """
96            Data is ready to be displayed
[4b91fd1]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             
[1bf33c1]102            @param event: data event
103        """
[6c0568b]104        ## Update self.data2d with the current plot
105        self.data2D = event.plot
[15550f4]106       
[1bf33c1]107        #TODO: Check for existence of plot attribute
[6c0568b]108       
[1bf33c1]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
[ab8f936]114       
[1bf33c1]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__:
[ab8f936]121                #overwrite a plottable using the same name
[1bf33c1]122                self.graph.delete(self.plots[event.plot.name])
123            else:
[ab8f936]124                # plottable is already draw on the panel
[1bf33c1]125                is_new = False
[ab8f936]126           
127        if is_new:
128            # a new plottable overwrites a plotted one  using the same id
129            for plottable in self.plots.itervalues():
[e48a62e]130                if hasattr(event.plot,"id"):
131                    if event.plot.id==plottable.id :
132                        self.graph.delete(plottable)
[ab8f936]133           
134            self.plots[event.plot.name] = event.plot
135            self.graph.add(self.plots[event.plot.name])
136        else:
[4b91fd1]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
[ac9a5f6]149            # update qmax with the new xmax of data plotted
150            self.qmax= event.plot.xmax
[4b91fd1]151           
[ac9a5f6]152        self.slicer= None
[1bf33c1]153        # Check axis labels
154        #TODO: Should re-factor this
[6c0568b]155        ## render the graph with its new content
[7fff5cd]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:
[b03deea]160                event.plot._xaxis = '\\rm{x}'
161                event.plot._yaxis = '\\rm{y}'
162                event.plot._xunit = 'pixel'
163                event.plot._yunit = 'pixel'
[7fff5cd]164
[1bf33c1]165        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
166        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
[0690e1d]167        self.graph.title(self.data2D.name)
[1bf33c1]168        self.graph.render(self)
169        self.subplot.figure.canvas.draw_idle()
[8dfdd20]170        ## store default value of zmin and zmax
171        self.default_zmin_ctl = self.zmin_2D
172        self.default_zmax_ctl = self.zmax_2D
[1bf33c1]173
174
175    def onContextMenu(self, event):
176        """
177            2D plot context menu
178            @param event: wx context event
179        """
[15550f4]180       
[1bf33c1]181        slicerpop = PanelMenu()
182        slicerpop.set_plots(self.plots)
183        slicerpop.set_graph(self.graph)
[9a585d0]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       
[1ce365f8]193        id = wx.NewId()
[18eba35]194        slicerpop.Append(id,'&Print Preview', 'image preview for print')
[1ce365f8]195        wx.EVT_MENU(self, id, self.onPrinterPreview)
196       
[9a585d0]197        slicerpop.AppendSeparator()
[7fff5cd]198        if len(self.data2D.detector) == 1:       
[0e13148]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           
[15550f4]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           
[92c2345]226            id = wx.NewId()
[15550f4]227            slicerpop.Append(id, '&Box Sum')
228            wx.EVT_MENU(self, id, self.onBoxSum) 
[6c0568b]229           
[15550f4]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 :
[eba08f1a]239                id = wx.NewId()
[15550f4]240                slicerpop.Append(id, '&Clear slicer')
241                wx.EVT_MENU(self, id,  self.onClearSlicer) 
[6c0568b]242               
[15550f4]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() 
[0e13148]249           
250        id = wx.NewId()
251        slicerpop.Append(id, '&Detector Parameters')
252        wx.EVT_MENU(self, id, self._onEditDetector) 
253       
[9a585d0]254       
[1bf33c1]255        id = wx.NewId()
256        slicerpop.Append(id, '&Toggle Linear/Log scale')
257        wx.EVT_MENU(self, id, self._onToggleScale) 
[d468daa]258                 
[1bf33c1]259        pos = event.GetPosition()
260        pos = self.ScreenToClient(pos)
261        self.PopupMenu(slicerpop, pos)
[8bd764d]262   
[6c0568b]263   
[ea290ee]264    def _onEditDetector(self, event):
[6d920cd]265        """
[6c0568b]266            Allow to view and edits  detector parameters
267            @param event: wx.menu event
[6d920cd]268        """
[6c0568b]269       
[ea290ee]270        import detector_dialog
[8dfdd20]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)
[6c0568b]275        ## info of current detector and data2D
[ea290ee]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
[e50f15b]282     
[6c0568b]283        ## set dialog window content
[ea290ee]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
[8dfdd20]292            self.cmap= evt.cmap
[ea290ee]293       
294        dialog.Destroy()
[6c0568b]295        ## Redraw the current image
[ea290ee]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,
[8dfdd20]303                   cmap= self.cmap,
[e50f15b]304                   color=0,symbol=0,label=self.data2D.name)#'data2D')
[ea290ee]305        self.subplot.figure.canvas.draw_idle()
[1bf33c1]306       
[6c0568b]307       
308 
[1bf33c1]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):
[6c0568b]318        pass 
319     
[1bf33c1]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):
[6c0568b]329        """
330            create an empty slicervent
331        """
[1bf33c1]332        return SlicerEvent(type=None,
333                           params=None,
334                           obj_class=None)
335    def _onEVT_INTERNAL(self, event):
336        """
[6c0568b]337            Draw the slicer
338            @param event: wx.lib.newevent (SlicerEvent) containing slicer
339            parameter
[1bf33c1]340        """
341        self._setSlicer(event.slicer)
342           
343    def _setSlicer(self, slicer):
[6c0568b]344        """
345            Clear the previous slicer and create a new one.Post an internal
346            event.
347            @param slicer: slicer class to create
348        """
[1bf33c1]349       
[6c0568b]350        ## Clear current slicer
[1bf33c1]351        if not self.slicer == None: 
352            self.slicer.clear()           
[6c0568b]353        ## Create a new slicer   
[1bf33c1]354        self.slicer_z += 1
355        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
[240c805]356        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
357        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
[6c0568b]358        ## Draw slicer
[1bf33c1]359        self.update()
360        self.slicer.update()
[6c0568b]361        wx.PostEvent(self.parent, StatusEvent(status=\
362                        "Plotter2D._setSlicer  %s"%self.slicer.__class__.__name__))
[1bf33c1]363        # Post slicer event
364        event = self._getEmptySlicerEvent()
365        event.type = self.slicer.__class__.__name__
[54cc36a]366       
[1bf33c1]367        event.obj_class = self.slicer.__class__
368        event.params = self.slicer.get_params()
[d468daa]369        wx.PostEvent(self, event)
[1bf33c1]370
[6c0568b]371
[1bf33c1]372    def onCircular(self, event):
373        """
374            perform circular averaging on Data2D
[6c0568b]375            @param event: wx.menu event
[1bf33c1]376        """
377       
378        from DataLoader.manipulations import CircularAverage
[6c0568b]379        ## compute the maximum radius of data2D
[216efab]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)) 
[6c0568b]383        ##Compute beam width
[8f584c9]384        bin_width = (self.qmax +self.qmax)/100
[6c0568b]385        ## Create data1D circular average of data2D
[c73d871]386        Circle = CircularAverage( r_min=0, r_max=self.radius, bin_width=bin_width)
[1bf33c1]387        circ = Circle(self.data2D)
[6c0568b]388       
[1bf33c1]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
[ef0c170]398       
[1bf33c1]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
[50cbace]402        #new_plot.info=self.data2D.info
[1bf33c1]403        new_plot.interactive = True
404        new_plot.detector =self.data2D.detector
[6c0568b]405        ## If the data file does not tell us what the axes are, just assume...
[8f584c9]406        new_plot.xaxis("\\rm{Q}","A^{-1}")
[1bf33c1]407        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
408        new_plot.group_id = "Circ avg "+ self.data2D.name
[8b30e62]409        new_plot.id = "Circ avg "+ self.data2D.name
[70cf5d3]410        new_plot.is_data= True
[6c0568b]411       
[1bf33c1]412        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))
[ef0c170]413       
[6c0568b]414       
[1bf33c1]415    def _onEditSlicer(self, event):
[6c0568b]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        """
[1bf33c1]422        if self.slicer !=None:
423            from SlicerParameters import SlicerParameterPanel
[4f8a00c]424            dialog = SlicerParameterPanel(self, -1, "Slicer Parameters")
[1bf33c1]425            dialog.set_slicer(self.slicer.__class__.__name__,
426                            self.slicer.get_params())
427            if dialog.ShowModal() == wx.ID_OK:
428                dialog.Destroy() 
429       
[6c0568b]430       
[1bf33c1]431    def onSectorQ(self, event):
432        """
[6c0568b]433            Perform sector averaging on Q and draw sector slicer
[1bf33c1]434        """
[ef0c170]435        from SectorSlicer import SectorInteractor
[1bf33c1]436        self.onClearSlicer(event)
[d468daa]437        wx.PostEvent(self, InternalEvent(slicer= SectorInteractor))
[1bf33c1]438       
439    def onSectorPhi(self, event):
440        """
[6c0568b]441            Perform sector averaging on Phi and draw annulus slicer
[1bf33c1]442        """
[ef0c170]443        from AnnulusSlicer import AnnulusInteractor
[1bf33c1]444        self.onClearSlicer(event)
[d468daa]445        wx.PostEvent(self, InternalEvent(slicer= AnnulusInteractor))
[1bf33c1]446       
[7ab9241]447    def onBoxSum(self,event):
448        from boxSum import BoxSum
449        self.onClearSlicer(event)
[6c0568b]450                   
[54cc36a]451        self.slicer_z += 1
452        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
[6c0568b]453       
[54cc36a]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()
[6c0568b]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
[8a7a21b]463        from slicerpanel import SlicerPanel
[6c0568b]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
[54cc36a]471        self.panel_slicer= new_panel
[6c0568b]472        ## save the window_caption of the new panel in the current slicer
[0bd2cd8]473        self.slicer.set_panel_name( name= new_panel.window_caption)
[6c0568b]474        ## post slicer panel to guiframe to display it
[54cc36a]475        from sans.guicomm.events import SlicerPanelEvent
[1debb29]476        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer,
477                                                    main_panel =self))
[6c0568b]478
[8a7a21b]479       
480    def onBoxavgX(self,event):
[6c0568b]481        """
482            Perform 2D data averaging on Qx
483            Create a new slicer .
484            @param event: wx.menu event
485        """
[8a7a21b]486        from boxSlicer import BoxInteractorX
[38224f10]487        self.onClearSlicer(event)
[d468daa]488        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorX))
489       
490       
[8a7a21b]491    def onBoxavgY(self,event):
[6c0568b]492        """
493            Perform 2D data averaging on Qy
494            Create a new slicer .
495            @param event: wx.menu event
496        """
[8a7a21b]497        from boxSlicer import BoxInteractorY
498        self.onClearSlicer(event)
[d468daa]499        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorY))
[6c0568b]500       
[b319def8]501       
[1bf33c1]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()
[d468daa]513            wx.PostEvent(self, event)
[1bf33c1]514         
[6c0568b]515   
[1bf33c1]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'
[6c0568b]524        self.image(self.data2D.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
[1bf33c1]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       
[0f6d05f8]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
[15550f4]545        """
Note: See TracBrowser for help on using the repository browser.