source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/Plotter2D.py @ fe48fcc

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

added 2D plot label modif

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