source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ a07e72f

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 a07e72f was a07e72f, checked in by Gervaise Alina <gervyh@…>, 13 years ago

remove other type of data into sansview

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