source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 957723f

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 957723f was 003fa4e, checked in by Jae Cho <jhjcho@…>, 14 years ago

Added dq in cir. averaging

  • Property mode set to 100644
File size: 21.5 KB
Line 
1
2################################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#
7#See the license text in license.txt
8#
9#copyright 2008, University of Tennessee
10################################################################################
11
12
13import wx
14import sys
15import os
16import math
17import numpy
18import pylab
19import danse.common.plottools
20from danse.common.plottools.PlotPanel import PlotPanel
21from danse.common.plottools.plottables import Graph
22from sans.guiframe.events import EVT_NEW_PLOT
23from sans.guiframe.events import EVT_SLICER_PARS
24from sans.guiframe.events import StatusEvent
25from sans.guiframe.events import NewPlotEvent
26from sans.guiframe.events import PanelOnFocusEvent
27from sans.guiframe.events import SlicerEvent
28from sans.guiframe.utils import PanelMenu
29from binder import BindArtist
30from Plotter1D import ModelPanel1D
31from danse.common.plottools.toolbar import NavigationToolBar
32from sans.guiframe.dataFitting import Data1D
33(InternalEvent, EVT_INTERNAL) = wx.lib.newevent.NewEvent()
34
35DEFAULT_QMAX = 0.05
36DEFAULT_QSTEP = 0.001
37DEFAULT_BEAM = 0.005
38BIN_WIDTH = 1.0
39
40
41class NavigationToolBar2D(NavigationToolBar):
42    """
43    """
44    def __init__(self, canvas, parent=None):
45        NavigationToolBar.__init__(self, canvas=canvas, parent=parent)
46       
47    def delete_option(self):
48        """
49        remove default toolbar item
50        """
51        #delete reset button
52        self.DeleteToolByPos(0) 
53        #delete dragging
54        self.DeleteToolByPos(2) 
55        #delete unwanted button that configures subplot parameters
56        self.DeleteToolByPos(4)
57       
58    def add_option(self):
59        """
60        add item to the toolbar
61        """
62        #add print button
63        id_print = wx.NewId()
64        print_bmp =  wx.ArtProvider.GetBitmap(wx.ART_PRINT, wx.ART_TOOLBAR)
65        self.AddSimpleTool(id_print, print_bmp,
66                           'Print', 'Activate printing')
67        wx.EVT_TOOL(self, id_print, self.on_print)
68       
69       
70class ModelPanel2D(ModelPanel1D):
71    """
72    Plot panel for use with the GUI manager
73    """
74   
75    ## Internal name for the AUI manager
76    window_name = "plotpanel"
77    ## Title to appear on top of the window
78    window_caption = "Plot Panel"
79    ## Flag to tell the GUI manager that this panel is not
80    #  tied to any perspective
81    ALWAYS_ON = True
82    ## Group ID
83    group_id = None
84   
85   
86    def __init__(self, parent, id=-1, data2d=None, color = None,
87                 dpi=None, style=wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
88        """
89        Initialize the panel
90        """
91        ModelPanel1D.__init__(self, parent, id=id, style=style, **kwargs)
92       
93        ## Reference to the parent window
94        self.parent = parent
95        ## Dictionary containing Plottables
96        self.plots = {}
97        ## Save reference of the current plotted
98        self.data2D = data2d
99        ## Unique ID (from gui_manager)
100        self.uid = None
101        ## Action IDs for internal call-backs
102        self.action_ids = {}
103        ## Create Artist and bind it
104        self.connect = BindArtist(self.subplot.figure)
105        ## Beam stop
106        self.beamstop_radius = DEFAULT_BEAM
107        ## to set the order of lines drawn first.
108        self.slicer_z = 5
109        ## Reference to the current slicer
110        self.slicer = None
111        ## event to send slicer info
112        self.Bind(EVT_INTERNAL, self._onEVT_INTERNAL)
113       
114        self.axes_frozen = False
115        ## panel that contains result from slicer motion (ex: Boxsum info)
116        self.panel_slicer = None
117        ## Graph       
118        self.graph = Graph()
119        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
120        self.graph.yaxis("\\rm{Intensity} ", "cm^{-1}")
121        self.graph.render(self)
122        ## store default value of zmin and zmax
123        self.default_zmin_ctl = self.zmin_2D
124        self.default_zmax_ctl = self.zmax_2D
125       
126    def onLeftDown(self, event): 
127        """
128        left button down and ready to drag
129       
130        """
131        # Check that the LEFT button was pressed
132        if event.button == 1:
133            self.leftdown = True
134            ax = event.inaxes
135            if ax != None:
136                self.xInit, self.yInit = event.xdata, event.ydata
137        self.plottable_selected(self.data2D.id)
138       
139        self._manager.set_panel_on_focus(self)
140        wx.PostEvent(self.parent, PanelOnFocusEvent(panel=self))
141       
142    def add_toolbar(self):
143        """
144        add toolbar
145        """
146        self.enable_toolbar = True
147        self.toolbar = NavigationToolBar2D(parent=self, canvas=self.canvas)
148        self.toolbar.Realize()
149        # On Windows platform, default window size is incorrect, so set
150        # toolbar width to figure width.
151        tw, th = self.toolbar.GetSizeTuple()
152        fw, fh = self.canvas.GetSizeTuple()
153        self.toolbar.SetSize(wx.Size(fw, th))
154        self.sizer.Add(self.toolbar, 0, wx.LEFT|wx.EXPAND)
155        # update the axes menu on the toolbar
156        self.toolbar.update()
157         
158    def plot_data(self, data):
159        """
160        Data is ready to be displayed
161       
162        :TODO: this name should be changed to something more appropriate
163             Don't forget that changing this name will mean changing code
164             in plotting.py
165         
166        :param event: data event
167        """
168        ## Update self.data2d with the current plot
169        self.data2D = data
170        if data.id in self.plots.keys():
171            #replace
172            self.graph.replace(data)
173            self.plots[data.id] = data
174        else:
175            self.plots[data.id] = data
176            self.graph.add(self.plots[data.id]) 
177            # update qmax with the new xmax of data plotted
178            self.qmax = data.xmax
179           
180        self.slicer = None
181        # Check axis labels
182        #TODO: Should re-factor this
183        ## render the graph with its new content
184               
185        #data2D: put 'Pixel (Number)' for axis title and unit in case of having no detector info and none in _units
186        if len(self.data2D.detector) < 1: 
187            if len(data._xunit) < 1 and len(data._yunit) < 1:
188                data._xaxis = '\\rm{x}'
189                data._yaxis = '\\rm{y}'
190                data._xunit = 'pixel'
191                data._yunit = 'pixel'
192        self.graph.xaxis(data._xaxis, data._xunit)
193        self.graph.yaxis(data._yaxis, data._yunit)
194        self.graph.title(self.data2D.name)
195        self.graph.render(self)
196        self.subplot.figure.canvas.draw_idle()
197        ## store default value of zmin and zmax
198        self.default_zmin_ctl = self.zmin_2D
199        self.default_zmax_ctl = self.zmax_2D
200
201    def onContextMenu(self, event):
202        """
203        2D plot context menu
204       
205        :param event: wx context event
206       
207        """
208        slicerpop = PanelMenu()
209        slicerpop.set_plots(self.plots)
210        slicerpop.set_graph(self.graph)
211             
212        id = wx.NewId()
213        slicerpop.Append(id, '&Save image')
214        wx.EVT_MENU(self, id, self.onSaveImage)
215       
216        id = wx.NewId()
217        slicerpop.Append(id,'&Print image', 'Print image')
218        wx.EVT_MENU(self, id, self.onPrint)
219       
220        id = wx.NewId()
221        slicerpop.Append(id,'&Print Preview', 'image preview for print')
222        wx.EVT_MENU(self, id, self.onPrinterPreview)
223
224        id = wx.NewId()
225        slicerpop.Append(id, '&Copy to Clipboard', 'Copy to the clipboard')
226        wx.EVT_MENU(self, id, self.OnCopyFigureMenu)
227       
228        # saving data
229        plot = self.data2D
230        id = wx.NewId()
231        name = plot.name
232        slicerpop.Append(id, "&Save as a file (DAT)" )
233        self.action_ids[str(id)] = plot
234        wx.EVT_MENU(self, id, self._onSave)
235
236        slicerpop.AppendSeparator()
237        if len(self.data2D.detector) == 1:       
238           
239            item_list = self.parent.get_context_menu(self)
240            if (not item_list == None) and (not len(item_list) == 0):
241                for item in item_list:
242                    try:
243                        id = wx.NewId()
244                        slicerpop.Append(id, item[0], item[1])
245                        wx.EVT_MENU(self, id, item[2])
246                    except:
247                        msg = "ModelPanel1D.onContextMenu: "
248                        msg += "bad menu item  %s"%sys.exc_value
249                        wx.PostEvent(self.parent, StatusEvent(status=msg))
250                        pass
251                slicerpop.AppendSeparator()
252           
253            id = wx.NewId()
254            slicerpop.Append(id, '&Perform circular average')
255            wx.EVT_MENU(self, id, self.onCircular) 
256            id = wx.NewId()
257            slicerpop.Append(id, '&Sector [Q view]')
258            wx.EVT_MENU(self, id, self.onSectorQ) 
259            id = wx.NewId()
260            slicerpop.Append(id, '&Annulus [Phi view ]')
261            wx.EVT_MENU(self, id, self.onSectorPhi) 
262            id = wx.NewId()
263            slicerpop.Append(id, '&Box Sum')
264            wx.EVT_MENU(self, id, self.onBoxSum) 
265            id = wx.NewId()
266            slicerpop.Append(id, '&Box averaging in Qx')
267            wx.EVT_MENU(self, id, self.onBoxavgX) 
268            id = wx.NewId()
269            slicerpop.Append(id, '&Box averaging in Qy')
270            wx.EVT_MENU(self, id, self.onBoxavgY) 
271            if self.slicer != None:
272                id = wx.NewId()
273                slicerpop.Append(id, '&Clear slicer')
274                wx.EVT_MENU(self, id,  self.onClearSlicer) 
275                if self.slicer.__class__.__name__  != "BoxSum":
276                    id = wx.NewId()
277                    slicerpop.Append(id, '&Edit Slicer Parameters')
278                    wx.EVT_MENU(self, id, self._onEditSlicer) 
279            slicerpop.AppendSeparator() 
280        id = wx.NewId()
281        slicerpop.Append(id, '&Detector Parameters')
282        wx.EVT_MENU(self, id, self._onEditDetector)
283        id = wx.NewId()
284        slicerpop.Append(id, '&Toggle Linear/Log scale')
285        wx.EVT_MENU(self, id, self._onToggleScale) 
286        pos = event.GetPosition()
287        pos = self.ScreenToClient(pos)
288        self.PopupMenu(slicerpop, pos)
289   
290    def _onEditDetector(self, event):
291        """
292        Allow to view and edits  detector parameters
293       
294        :param event: wx.menu event
295       
296        """
297        import detector_dialog
298        dialog = detector_dialog.DetectorDialog(self, -1,base=self.parent,
299                       reset_zmin_ctl =self.default_zmin_ctl,
300                       reset_zmax_ctl = self.default_zmax_ctl,cmap=self.cmap)
301        ## info of current detector and data2D
302        xnpts = len(self.data2D.x_bins)
303        ynpts = len(self.data2D.y_bins)
304        xmax = max(self.data2D.xmin, self.data2D.xmax)
305        ymax = max(self.data2D.ymin, self.data2D.ymax)
306        qmax = math.sqrt(math.pow(xmax, 2) + math.pow(ymax, 2))
307        beam = self.data2D.xmin
308        ## set dialog window content
309        dialog.setContent(xnpts=xnpts,ynpts=ynpts,qmax=qmax,
310                           beam=self.data2D.xmin,
311                           zmin = self.zmin_2D,
312                          zmax = self.zmax_2D)
313        if dialog.ShowModal() == wx.ID_OK:
314            evt = dialog.getContent()
315            self.zmin_2D = evt.zmin
316            self.zmax_2D = evt.zmax
317            self.cmap = evt.cmap
318        dialog.Destroy()
319        ## Redraw the current image
320        self.image(data=self.data2D.data,
321                   qx_data=self.data2D.qx_data,
322                   qy_data=self.data2D.qy_data,
323                   xmin= self.data2D.xmin,
324                   xmax= self.data2D.xmax,
325                   ymin= self.data2D.ymin,
326                   ymax= self.data2D.ymax,
327                   zmin= self.zmin_2D,
328                   zmax= self.zmax_2D,
329                   cmap= self.cmap,
330                   color=0, symbol=0, label=self.data2D.name)
331        self.subplot.figure.canvas.draw_idle()
332       
333    def freeze_axes(self):
334        """
335        """
336        self.axes_frozen = True
337       
338    def thaw_axes(self):
339        """
340        """
341        self.axes_frozen = False
342       
343    def onMouseMotion(self,event):
344        """
345        """
346        pass
347   
348    def onWheel(self, event):
349        """
350        """
351        pass 
352     
353    def update(self, draw=True):
354        """
355        Respond to changes in the model by recalculating the
356        profiles and resetting the widgets.
357        """
358        self.draw()
359       
360    def _getEmptySlicerEvent(self):
361        """
362        create an empty slicervent
363        """
364        return SlicerEvent(type=None, params=None, obj_class=None)
365       
366    def _onEVT_INTERNAL(self, event):
367        """
368        Draw the slicer
369       
370        :param event: wx.lib.newevent (SlicerEvent) containing slicer
371            parameter
372           
373        """
374        self._setSlicer(event.slicer)
375           
376    def _setSlicer(self, slicer):
377        """
378        Clear the previous slicer and create a new one.Post an internal
379        event.
380       
381        :param slicer: slicer class to create
382       
383        """
384        ## Clear current slicer
385        if not self.slicer == None: 
386            self.slicer.clear()           
387        ## Create a new slicer   
388        self.slicer_z += 1
389        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
390        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
391        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
392        ## Draw slicer
393        self.update()
394        self.slicer.update()
395        msg = "Plotter2D._setSlicer  %s"%self.slicer.__class__.__name__
396        wx.PostEvent(self.parent, StatusEvent(status=msg))
397        # Post slicer event
398        event = self._getEmptySlicerEvent()
399        event.type = self.slicer.__class__.__name__
400        event.obj_class = self.slicer.__class__
401        event.params = self.slicer.get_params()
402        wx.PostEvent(self, event)
403
404    def onCircular(self, event):
405        """
406        perform circular averaging on Data2D
407       
408        :param event: wx.menu event
409       
410        """
411        # Find the best number of bins
412        npt = math.sqrt(len(self.data2D.data[numpy.isfinite(self.data2D.data)]))
413        npt = math.floor(npt)
414        from DataLoader.manipulations import CircularAverage
415        ## compute the maximum radius of data2D
416        self.qmax = max(math.fabs(self.data2D.xmax), 
417                        math.fabs(self.data2D.xmin))
418        self.ymax = max(math.fabs(self.data2D.ymax),
419                        math.fabs(self.data2D.ymin))
420        self.radius = math.sqrt(math.pow(self.qmax, 2)+ math.pow(self.ymax, 2)) 
421        ##Compute beam width
422        bin_width = (self.qmax + self.qmax)/npt
423        ## Create data1D circular average of data2D
424        Circle = CircularAverage(r_min=0, r_max=self.radius, 
425                                 bin_width=bin_width)
426        circ = Circle(self.data2D)
427        from sans.guiframe.dataFitting import Data1D
428        if hasattr(circ, "dxl"):
429            dxl = circ.dxl
430        else:
431            dxl = None
432        if hasattr(circ, "dxw"):
433            dxw = circ.dxw
434        else:
435            dxw = None
436
437        new_plot = Data1D(x=circ.x, y=circ.y, dy=circ.dy, dx=circ.dx)
438        new_plot.dxl = dxl
439        new_plot.dxw = dxw
440        new_plot.name = "Circ avg " + self.data2D.name
441        new_plot.source = self.data2D.source
442        #new_plot.info = self.data2D.info
443        new_plot.interactive = True
444        new_plot.detector = self.data2D.detector
445        ## If the data file does not tell us what the axes are, just assume...
446        new_plot.xaxis("\\rm{Q}", "A^{-1}")
447        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
448        new_plot.group_id = "Circ avg " + self.data2D.name
449        new_plot.id = "Circ avg " + self.data2D.name
450        new_plot.is_data = True
451        wx.PostEvent(self.parent, 
452                     NewPlotEvent(plot=new_plot, title=new_plot.name))
453       
454    def _onEditSlicer(self, event):
455        """
456        Is available only when a slicer is drawn.Create a dialog
457        window where the user can enter value to reset slicer
458        parameters.
459       
460        :param event: wx.menu event
461       
462        """
463        if self.slicer != None:
464            from SlicerParameters import SlicerParameterPanel
465            dialog = SlicerParameterPanel(self, -1, "Slicer Parameters")
466            dialog.set_slicer(self.slicer.__class__.__name__,
467                            self.slicer.get_params())
468            if dialog.ShowModal() == wx.ID_OK:
469                dialog.Destroy() 
470       
471    def onSectorQ(self, event):
472        """
473        Perform sector averaging on Q and draw sector slicer
474        """
475        from SectorSlicer import SectorInteractor
476        self.onClearSlicer(event)
477        wx.PostEvent(self, InternalEvent(slicer=SectorInteractor))
478       
479    def onSectorPhi(self, event):
480        """
481        Perform sector averaging on Phi and draw annulus slicer
482        """
483        from AnnulusSlicer import AnnulusInteractor
484        self.onClearSlicer(event)
485        wx.PostEvent(self, InternalEvent(slicer=AnnulusInteractor))
486       
487    def onBoxSum(self, event):
488        """
489        """
490        from boxSum import BoxSum
491        self.onClearSlicer(event)
492        self.slicer_z += 1
493        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
494        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
495        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
496        self.update()
497        self.slicer.update()
498        ## Value used to initially set the slicer panel
499        type = self.slicer.__class__.__name__
500        params = self.slicer.get_params()
501        ## Create a new panel to display results of summation of Data2D
502        from slicerpanel import SlicerPanel
503        new_panel = SlicerPanel(parent=self.parent, id=-1,
504                                    base=self, type=type,
505                                    params=params, style=wx.RAISED_BORDER)
506       
507        new_panel.window_caption = self.slicer.__class__.__name__ + " " + \
508                                    str(self.data2D.name)
509        new_panel.window_name = self.slicer.__class__.__name__+ " " + \
510                                    str(self.data2D.name)
511        ## Store a reference of the new created panel
512        self.panel_slicer = new_panel
513        ## save the window_caption of the new panel in the current slicer
514        self.slicer.set_panel_name(name=new_panel.window_caption)
515        ## post slicer panel to guiframe to display it
516        from sans.guiframe.events import SlicerPanelEvent
517        wx.PostEvent(self.parent, SlicerPanelEvent(panel=self.panel_slicer,
518                                                    main_panel=self))
519
520    def onBoxavgX(self,event):
521        """
522        Perform 2D data averaging on Qx
523        Create a new slicer .
524       
525        :param event: wx.menu event
526        """
527        from boxSlicer import BoxInteractorX
528        self.onClearSlicer(event)
529        wx.PostEvent(self, InternalEvent(slicer=BoxInteractorX))
530       
531    def onBoxavgY(self,event):
532        """
533        Perform 2D data averaging on Qy
534        Create a new slicer .
535       
536        :param event: wx.menu event
537       
538        """
539        from boxSlicer import BoxInteractorY
540        self.onClearSlicer(event)
541        wx.PostEvent(self, InternalEvent(slicer=BoxInteractorY))
542       
543    def onClearSlicer(self, event):
544        """
545        Clear the slicer on the plot
546        """
547        if not self.slicer == None:
548            self.slicer.clear()
549            self.subplot.figure.canvas.draw()
550            self.slicer = None
551            # Post slicer None event
552            event = self._getEmptySlicerEvent()
553            wx.PostEvent(self, event)
554           
555    def _onSave(self, evt):
556        """
557        Save a data set to a dat(text) file
558       
559        :param evt: Menu event
560       
561        """
562        id = str(evt.GetId())
563        if id in self.action_ids:         
564           
565            path = None
566            wildcard = "IGOR/DAT 2D file in Q_map (*.dat)|*.DAT"
567            dlg = wx.FileDialog(self, "Choose a file",
568                                self._default_save_location,
569                                 "", wildcard , wx.SAVE)
570           
571            if dlg.ShowModal() == wx.ID_OK:
572                path = dlg.GetPath()
573                mypath = os.path.basename(path)
574               
575                #TODO: This is bad design. The DataLoader is designed
576                #to recognize extensions.
577                # It should be a simple matter of calling the .
578                #save(file, data, '.xml') method
579                # of the DataLoader.loader.Loader class.
580                from DataLoader.loader import  Loader
581                #Instantiate a loader
582                loader = Loader() 
583                data = self.data2D
584
585                format = ".dat"
586                if os.path.splitext(mypath)[1].lower() == format:
587                    loader.save(path, data, format)
588                try:
589                    self._default_save_location = os.path.dirname(path)
590                except:
591                    pass   
592            dlg.Destroy()
Note: See TracBrowser for help on using the repository browser.