source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 626a1f9

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 626a1f9 was 626a1f9, checked in by Jae Cho <jhjcho@…>, 13 years ago

temp commit for mac event 2D click

  • Property mode set to 100644
File size: 23.0 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        print "self.data2D.id=", self.data2D.id, "button=", event.button
138        self.plottable_selected(self.data2D.id)
139       
140        self._manager.set_panel_on_focus(self)
141        wx.PostEvent(self.parent, PanelOnFocusEvent(panel=self))
142       
143    def add_toolbar(self):
144        """
145        add toolbar
146        """
147        self.enable_toolbar = True
148        self.toolbar = NavigationToolBar2D(parent=self, canvas=self.canvas)
149        self.toolbar.Realize()
150        # On Windows platform, default window size is incorrect, so set
151        # toolbar width to figure width.
152        tw, th = self.toolbar.GetSizeTuple()
153        fw, fh = self.canvas.GetSizeTuple()
154        self.toolbar.SetSize(wx.Size(fw, th))
155        self.sizer.Add(self.toolbar, 0, wx.LEFT|wx.EXPAND)
156        # update the axes menu on the toolbar
157        self.toolbar.update()
158         
159    def plot_data(self, data):
160        """
161        Data is ready to be displayed
162       
163        :TODO: this name should be changed to something more appropriate
164             Don't forget that changing this name will mean changing code
165             in plotting.py
166         
167        :param event: data event
168        """
169        ## Update self.data2d with the current plot
170        self.data2D = data
171        if data.id in self.plots.keys():
172            #replace
173            self.graph.replace(data)
174            self.plots[data.id] = data
175        else:
176            self.plots[data.id] = data
177            self.graph.add(self.plots[data.id]) 
178            # update qmax with the new xmax of data plotted
179            self.qmax = data.xmax
180           
181        self.slicer = None
182        # Check axis labels
183        #TODO: Should re-factor this
184        ## render the graph with its new content
185               
186        #data2D: put 'Pixel (Number)' for axis title and unit in case of having no detector info and none in _units
187        if len(self.data2D.detector) < 1: 
188            if len(data._xunit) < 1 and len(data._yunit) < 1:
189                data._xaxis = '\\rm{x}'
190                data._yaxis = '\\rm{y}'
191                data._xunit = 'pixel'
192                data._yunit = 'pixel'
193        self.graph.xaxis(data._xaxis, data._xunit)
194        self.graph.yaxis(data._yaxis, data._yunit)
195        self.graph.title(self.data2D.name)
196        self.graph.render(self)
197        self.draw_plot()
198        #self.subplot.figure.canvas.draw_idle()
199        ## store default value of zmin and zmax
200        self.default_zmin_ctl = self.zmin_2D
201        self.default_zmax_ctl = self.zmax_2D
202
203    def onContextMenu(self, event):
204        """
205        2D plot context menu
206       
207        :param event: wx context event
208       
209        """
210        slicerpop = PanelMenu()
211        slicerpop.set_plots(self.plots)
212        slicerpop.set_graph(self.graph)
213             
214        id = wx.NewId()
215        slicerpop.Append(id, '&Save Image')
216        wx.EVT_MENU(self, id, self.onSaveImage)
217       
218        id = wx.NewId()
219        slicerpop.Append(id,'&Print Image', 'Print image')
220        wx.EVT_MENU(self, id, self.onPrint)
221       
222        id = wx.NewId()
223        slicerpop.Append(id,'&Print Preview', 'Print preview')
224        wx.EVT_MENU(self, id, self.onPrinterPreview)
225
226        id = wx.NewId()
227        slicerpop.Append(id, '&Copy to Clipboard', 'Copy to the clipboard')
228        wx.EVT_MENU(self, id, self.OnCopyFigureMenu)
229        slicerpop.AppendSeparator()
230        # saving data
231        plot = self.data2D
232        id = wx.NewId()
233        name = plot.name
234        slicerpop.Append(id, "&Save as a file (DAT)" )
235        self.action_ids[str(id)] = plot
236        wx.EVT_MENU(self, id, self._onSave)
237
238        slicerpop.AppendSeparator()
239        if len(self.data2D.detector) == 1:       
240           
241            item_list = self.parent.get_context_menu(self)
242            if (not item_list == None) and (not len(item_list) == 0) and\
243                self.data2D.name.split(" ")[0] != 'Residuals': 
244                # The line above; Not for trunk
245                for item in item_list:
246                    try:
247                        id = wx.NewId()
248                        slicerpop.Append(id, item[0], item[1])
249                        wx.EVT_MENU(self, id, item[2])
250                    except:
251                        msg = "ModelPanel1D.onContextMenu: "
252                        msg += "bad menu item  %s"%sys.exc_value
253                        wx.PostEvent(self.parent, StatusEvent(status=msg))
254                        pass
255                slicerpop.AppendSeparator()
256           
257            id = wx.NewId()
258            slicerpop.Append(id, '&Perform circular average')
259            wx.EVT_MENU(self, id, self.onCircular) \
260            # For Masked Data
261            if not plot.mask.all():
262                id = wx.NewId()
263                slicerpop.Append(id, '&Masked circular average')
264                wx.EVT_MENU(self, id, self.onMaskedCircular) 
265            id = wx.NewId()
266            slicerpop.Append(id, '&Sector [Q view]')
267            wx.EVT_MENU(self, id, self.onSectorQ) 
268            id = wx.NewId()
269            slicerpop.Append(id, '&Annulus [Phi view ]')
270            wx.EVT_MENU(self, id, self.onSectorPhi) 
271            id = wx.NewId()
272            slicerpop.Append(id, '&Box Sum')
273            wx.EVT_MENU(self, id, self.onBoxSum) 
274            id = wx.NewId()
275            slicerpop.Append(id, '&Box averaging in Qx')
276            wx.EVT_MENU(self, id, self.onBoxavgX) 
277            id = wx.NewId()
278            slicerpop.Append(id, '&Box averaging in Qy')
279            wx.EVT_MENU(self, id, self.onBoxavgY) 
280            if self.slicer != None:
281                id = wx.NewId()
282                slicerpop.Append(id, '&Clear slicer')
283                wx.EVT_MENU(self, id,  self.onClearSlicer) 
284                if self.slicer.__class__.__name__  != "BoxSum":
285                    id = wx.NewId()
286                    slicerpop.Append(id, '&Edit Slicer Parameters')
287                    wx.EVT_MENU(self, id, self._onEditSlicer) 
288            slicerpop.AppendSeparator() 
289        id = wx.NewId()
290        slicerpop.Append(id, '&2D Color Map')
291        wx.EVT_MENU(self, id, self._onEditDetector)
292        id = wx.NewId()
293        slicerpop.Append(id, '&Toggle Linear/Log scale')
294        wx.EVT_MENU(self, id, self._onToggleScale) 
295        pos = event.GetPosition()
296        pos = self.ScreenToClient(pos)
297        self.PopupMenu(slicerpop, pos)
298   
299    def _onEditDetector(self, event):
300        """
301        Allow to view and edits  detector parameters
302       
303        :param event: wx.menu event
304       
305        """
306        import detector_dialog
307        dialog = detector_dialog.DetectorDialog(self, -1,base=self.parent,
308                       reset_zmin_ctl =self.default_zmin_ctl,
309                       reset_zmax_ctl = self.default_zmax_ctl,cmap=self.cmap)
310        ## info of current detector and data2D
311        xnpts = len(self.data2D.x_bins)
312        ynpts = len(self.data2D.y_bins)
313        xmax = max(self.data2D.xmin, self.data2D.xmax)
314        ymax = max(self.data2D.ymin, self.data2D.ymax)
315        qmax = math.sqrt(math.pow(xmax, 2) + math.pow(ymax, 2))
316        beam = self.data2D.xmin
317        ## set dialog window content
318        dialog.setContent(xnpts=xnpts,ynpts=ynpts,qmax=qmax,
319                           beam=self.data2D.xmin,
320                           zmin = self.zmin_2D,
321                          zmax = self.zmax_2D)
322        if dialog.ShowModal() == wx.ID_OK:
323            evt = dialog.getContent()
324            self.zmin_2D = evt.zmin
325            self.zmax_2D = evt.zmax
326            self.cmap = evt.cmap
327        dialog.Destroy()
328        ## Redraw the current image
329        self.image(data=self.data2D.data,
330                   qx_data=self.data2D.qx_data,
331                   qy_data=self.data2D.qy_data,
332                   xmin= self.data2D.xmin,
333                   xmax= self.data2D.xmax,
334                   ymin= self.data2D.ymin,
335                   ymax= self.data2D.ymax,
336                   zmin= self.zmin_2D,
337                   zmax= self.zmax_2D,
338                   cmap= self.cmap,
339                   color=0, symbol=0, label=self.data2D.name)
340        self.subplot.figure.canvas.draw_idle()
341       
342    def freeze_axes(self):
343        """
344        """
345        self.axes_frozen = True
346       
347    def thaw_axes(self):
348        """
349        """
350        self.axes_frozen = False
351       
352    def onMouseMotion(self,event):
353        """
354        """
355        pass
356   
357    def onWheel(self, event):
358        """
359        """
360        pass 
361     
362    def update(self, draw=True):
363        """
364        Respond to changes in the model by recalculating the
365        profiles and resetting the widgets.
366        """
367        self.draw_plot()
368       
369    def _getEmptySlicerEvent(self):
370        """
371        create an empty slicervent
372        """
373        return SlicerEvent(type=None, params=None, obj_class=None)
374       
375    def _onEVT_INTERNAL(self, event):
376        """
377        Draw the slicer
378       
379        :param event: wx.lib.newevent (SlicerEvent) containing slicer
380            parameter
381           
382        """
383        self._setSlicer(event.slicer)
384           
385    def _setSlicer(self, slicer):
386        """
387        Clear the previous slicer and create a new one.Post an internal
388        event.
389       
390        :param slicer: slicer class to create
391       
392        """
393        ## Clear current slicer
394        if not self.slicer == None: 
395            self.slicer.clear()           
396        ## Create a new slicer   
397        self.slicer_z += 1
398        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
399        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
400        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
401        ## Draw slicer
402        self.update()
403        self.slicer.update()
404        msg = "Plotter2D._setSlicer  %s"%self.slicer.__class__.__name__
405        wx.PostEvent(self.parent, StatusEvent(status=msg))
406        # Post slicer event
407        event = self._getEmptySlicerEvent()
408        event.type = self.slicer.__class__.__name__
409        event.obj_class = self.slicer.__class__
410        event.params = self.slicer.get_params()
411        wx.PostEvent(self, event)
412       
413    def onMaskedCircular(self, event):
414        """
415        perform circular averaging on Data2D with mask if it exists
416       
417        :param event: wx.menu event
418       
419        """
420        self.onCircular(event, True)
421       
422    def onCircular(self, event, ismask=False):
423        """
424        perform circular averaging on Data2D
425       
426        :param event: wx.menu event
427       
428        """
429        # Find the best number of bins
430        npt = math.sqrt(len(self.data2D.data[numpy.isfinite(self.data2D.data)]))
431        npt = math.floor(npt)
432        from DataLoader.manipulations import CircularAverage
433        ## compute the maximum radius of data2D
434        self.qmax = max(math.fabs(self.data2D.xmax), 
435                        math.fabs(self.data2D.xmin))
436        self.ymax = max(math.fabs(self.data2D.ymax),
437                        math.fabs(self.data2D.ymin))
438        self.radius = math.sqrt(math.pow(self.qmax, 2)+ math.pow(self.ymax, 2)) 
439        ##Compute beam width
440        bin_width = (self.qmax + self.qmax)/npt
441        ## Create data1D circular average of data2D
442        Circle = CircularAverage(r_min=0, r_max=self.radius, 
443                                 bin_width=bin_width)
444        circ = Circle(self.data2D, ismask=ismask)
445        from sans.guiframe.dataFitting import Data1D
446        if hasattr(circ, "dxl"):
447            dxl = circ.dxl
448        else:
449            dxl = None
450        if hasattr(circ, "dxw"):
451            dxw = circ.dxw
452        else:
453            dxw = None
454
455        new_plot = Data1D(x=circ.x, y=circ.y, dy=circ.dy, dx=circ.dx)
456        new_plot.dxl = dxl
457        new_plot.dxw = dxw
458        new_plot.name = "Circ avg " + self.data2D.name
459        new_plot.source = self.data2D.source
460        #new_plot.info = self.data2D.info
461        new_plot.interactive = True
462        new_plot.detector = self.data2D.detector
463       
464        ## If the data file does not tell us what the axes are, just assume...
465        new_plot.xaxis("\\rm{Q}", "A^{-1}")
466        if hasattr(self.data2D, "scale") and \
467                    self.data2D.scale == 'linear':
468            new_plot.ytransform = 'y'
469            new_plot.yaxis("\\rm{Residuals} ", "normalized")
470        else:
471            new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
472
473        new_plot.group_id = "Circ avg " + self.data2D.name
474        new_plot.id = "Circ avg " + self.data2D.name
475        new_plot.is_data = True
476        wx.PostEvent(self.parent, 
477                     NewPlotEvent(plot=new_plot, title=new_plot.name))
478       
479    def _onEditSlicer(self, event):
480        """
481        Is available only when a slicer is drawn.Create a dialog
482        window where the user can enter value to reset slicer
483        parameters.
484       
485        :param event: wx.menu event
486       
487        """
488        if self.slicer != None:
489            from SlicerParameters import SlicerParameterPanel
490            dialog = SlicerParameterPanel(self, -1, "Slicer Parameters")
491            dialog.set_slicer(self.slicer.__class__.__name__,
492                            self.slicer.get_params())
493            if dialog.ShowModal() == wx.ID_OK:
494                dialog.Destroy() 
495       
496    def onSectorQ(self, event):
497        """
498        Perform sector averaging on Q and draw sector slicer
499        """
500        from SectorSlicer import SectorInteractor
501        self.onClearSlicer(event)
502        wx.PostEvent(self, InternalEvent(slicer=SectorInteractor))
503       
504    def onSectorPhi(self, event):
505        """
506        Perform sector averaging on Phi and draw annulus slicer
507        """
508        from AnnulusSlicer import AnnulusInteractor
509        self.onClearSlicer(event)
510        wx.PostEvent(self, InternalEvent(slicer=AnnulusInteractor))
511       
512    def onBoxSum(self, event):
513        """
514        """
515        from boxSum import BoxSum
516        self.onClearSlicer(event)
517        self.slicer_z += 1
518        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
519        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
520        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
521        self.update()
522        self.slicer.update()
523        ## Value used to initially set the slicer panel
524        type = self.slicer.__class__.__name__
525        params = self.slicer.get_params()
526        ## Create a new panel to display results of summation of Data2D
527        from slicerpanel import SlicerPanel
528        new_panel = SlicerPanel(parent=self.parent, id=-1,
529                                    base=self, type=type,
530                                    params=params, style=wx.RAISED_BORDER)
531       
532        new_panel.window_caption = self.slicer.__class__.__name__ + " " + \
533                                    str(self.data2D.name)
534        new_panel.window_name = self.slicer.__class__.__name__+ " " + \
535                                    str(self.data2D.name)
536        ## Store a reference of the new created panel
537        self.panel_slicer = new_panel
538        ## save the window_caption of the new panel in the current slicer
539        self.slicer.set_panel_name(name=new_panel.window_caption)
540        ## post slicer panel to guiframe to display it
541        from sans.guiframe.events import SlicerPanelEvent
542        wx.PostEvent(self.parent, SlicerPanelEvent(panel=self.panel_slicer,
543                                                    main_panel=self))
544
545    def onBoxavgX(self,event):
546        """
547        Perform 2D data averaging on Qx
548        Create a new slicer .
549       
550        :param event: wx.menu event
551        """
552        from boxSlicer import BoxInteractorX
553        self.onClearSlicer(event)
554        wx.PostEvent(self, InternalEvent(slicer=BoxInteractorX))
555       
556    def onBoxavgY(self,event):
557        """
558        Perform 2D data averaging on Qy
559        Create a new slicer .
560       
561        :param event: wx.menu event
562       
563        """
564        from boxSlicer import BoxInteractorY
565        self.onClearSlicer(event)
566        wx.PostEvent(self, InternalEvent(slicer=BoxInteractorY))
567       
568    def onClearSlicer(self, event):
569        """
570        Clear the slicer on the plot
571        """
572        if not self.slicer == None:
573            self.slicer.clear()
574            self.subplot.figure.canvas.draw()
575            self.slicer = None
576            # Post slicer None event
577            event = self._getEmptySlicerEvent()
578            wx.PostEvent(self, event)
579           
580    def _onSave(self, evt):
581        """
582        Save a data set to a dat(text) file
583       
584        :param evt: Menu event
585       
586        """
587        id = str(evt.GetId())
588        if id in self.action_ids:         
589           
590            path = None
591            wildcard = "IGOR/DAT 2D file in Q_map (*.dat)|*.DAT"
592            dlg = wx.FileDialog(self, "Choose a file",
593                                self._default_save_location,
594                                 "", wildcard , wx.SAVE)
595           
596            if dlg.ShowModal() == wx.ID_OK:
597                path = dlg.GetPath()
598                # ext_num = 0 for .txt, ext_num = 1 for .xml
599                # This is MAC Fix
600                ext_num = dlg.GetFilterIndex()
601                if ext_num == 0:
602                    format = '.dat'
603                else:
604                    format = ''
605                path = os.path.splitext(path)[0] + format
606                mypath = os.path.basename(path)
607               
608                #TODO: This is bad design. The DataLoader is designed
609                #to recognize extensions.
610                # It should be a simple matter of calling the .
611                #save(file, data, '.xml') method
612                # of the DataLoader.loader.Loader class.
613                from DataLoader.loader import  Loader
614                #Instantiate a loader
615                loader = Loader() 
616                data = self.data2D
617
618                format = ".dat"
619                if os.path.splitext(mypath)[1].lower() == format:
620                    # Make sure the ext included in the file name
621                    # especially on MAC
622                    fName = os.path.splitext(path)[0] + format
623                    loader.save(fName, data, format)
624                try:
625                    self._default_save_location = os.path.dirname(path)
626                except:
627                    pass   
628            dlg.Destroy()
Note: See TracBrowser for help on using the repository browser.