source: sasview/sansview/perspectives/fitting/fitting.py @ 784e2fa

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

make sur the fitpage is close when removing data1D from the graph

  • Property mode set to 100644
File size: 49.7 KB
Line 
1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2009, University of Tennessee
9"""
10import  re
11import sys, wx, logging
12import string, numpy, math
13import time
14import thread
15from copy import deepcopy
16
17from danse.common.plottools.PlotPanel import PlotPanel
18
19from sans.guiframe.dataFitting import Data2D
20from sans.guiframe.dataFitting import Data1D
21from sans.guiframe.dataFitting import Theory1D
22
23from sans.guiframe.utils import format_number
24
25from sans.guicomm.events import NewPlotEvent, StatusEvent 
26from sans.guicomm.events import EVT_SLICER_PANEL,ERR_DATA,EVT_REMOVE_DATA
27from sans.guicomm.events import EVT_SLICER_PARS_UPDATE
28
29from sans.fit.AbstractFitEngine import Model
30from sans.fit.AbstractFitEngine import FitAbort
31from console import ConsoleUpdate
32
33from fitproblem import FitProblem
34from fitpanel import FitPanel
35from fit_thread import FitThread
36import models
37import fitpage
38
39DEFAULT_BEAM = 0.005
40DEFAULT_QMIN = 0.001
41DEFAULT_QMAX = 0.13
42DEFAULT_NPTS = 50
43
44(PageInfoEvent, EVT_PAGE_INFO)   = wx.lib.newevent.NewEvent()
45
46
47class PlotInfo:
48    """
49        store some plotting field
50    """
51    _xunit = 'A^{-1}'
52    _xaxis= "\\rm{Q}"
53    _yunit = "cm^{-1}"
54    _yaxis= "\\rm{Intensity} "
55    id = "Model"
56    group_id = "Model"
57    title= None
58    info= None
59   
60   
61class Plugin:
62    """
63        Fitting plugin is used to perform fit
64    """
65    def __init__(self):
66        ## Plug-in name
67        self.sub_menu = "Fitting"
68       
69        ## Reference to the parent window
70        self.parent = None
71        #Provide list of models existing in the application
72        self.menu_mng = models.ModelManager()
73        ## List of panels for the simulation perspective (names)
74        self.perspective = []
75        #list of panel to send to guiframe
76        self.mypanels=[]
77        # reference to the current running thread
78        self.calc_2D= None
79        self.calc_1D= None
80        self.calc_fit= None
81       
82        # Start with a good default
83        self.elapsed = 0.022
84        # the type of optimizer selected, park or scipy
85        self.fitter  = None
86        #let fit ready
87        self.fitproblem_count = None
88        #Flag to let the plug-in know that it is running stand alone
89        self.standalone=True
90        ## dictionary of page closed and id
91        self.closed_page_dict ={}
92        ## Fit engine
93        self._fit_engine = 'scipy'
94        #List of selected data
95        self.selected_data_list=[]
96        ## list of slicer panel created to display slicer parameters and results
97        self.slicer_panels=[]
98        # Log startup
99        logging.info("Fitting plug-in started")   
100        # model 2D view
101        self.model2D_id=None
102        #keep reference of the simultaneous fit page
103        self.sim_page=None
104        #dictionary containing data name and error on dy of that data
105        self.err_dy = {}
106       
107   
108       
109    def populate_menu(self, id, owner):
110        """
111            Create a menu for the Fitting plug-in
112            @param id: id to create a menu
113            @param owner: owner of menu
114            @ return : list of information to populate the main menu
115        """
116        #Menu for fitting
117        self.menu1 = wx.Menu()
118       
119        #Set park engine
120        id3 = wx.NewId()
121        scipy_help= "Scipy Engine: Perform Simple fit. More in Help window...."
122        self.menu1.AppendCheckItem(id3, "Simple Fit  [Scipy]",scipy_help) 
123        wx.EVT_MENU(owner, id3,  self._onset_engine_scipy)
124       
125        id3 = wx.NewId()
126        park_help = "Park Engine: Perform Complex fit. More in Help window...."
127        self.menu1.AppendCheckItem(id3, "Complex Fit  [Park]",park_help) 
128        wx.EVT_MENU(owner, id3,  self._onset_engine_park)
129       
130        self.menu1.FindItemByPosition(0).Check(True)
131        self.menu1.FindItemByPosition(1).Check(False)
132           
133        self.menu1.AppendSeparator()
134        id1 = wx.NewId()
135        simul_help = "Allow to edit fit engine with multiple model and data"
136        self.menu1.Append(id1, '&Simultaneous Page',simul_help)
137        wx.EVT_MENU(owner, id1, self.on_add_sim_page)
138       
139        #menu for model
140        menu2 = wx.Menu()
141        self.menu_mng.populate_menu(menu2, owner)
142        id2 = wx.NewId()
143        owner.Bind(models.EVT_MODEL,self._on_model_menu)
144     
145        self.fit_panel.set_owner(owner)
146        self.fit_panel.set_model_list(self.menu_mng.get_model_list())
147        owner.Bind(fitpage.EVT_MODEL_BOX,self._on_model_panel)
148     
149        #create  menubar items
150        return [(id, self.menu1, "Fitting")]
151               
152    def on_add_sim_page(self, event):
153        """
154            Create a page to access simultaneous fit option
155        """
156        Plugin.on_perspective(self,event=event)
157        if self.sim_page !=None:
158            msg= "Simultaneous Fit page already opened"
159            wx.PostEvent(self.parent, StatusEvent(status= msg))
160            return 
161       
162        self.sim_page= self.fit_panel.add_sim_page()
163       
164    def help(self, evt):
165        """
166            Show a general help dialog.
167            TODO: replace the text with a nice image
168        """
169       
170        from help_panel import  HelpWindow
171        frame = HelpWindow(None, -1, 'HelpWindow')   
172        frame.Show(True)
173       
174       
175    def get_context_menu(self, graph=None):
176        """
177            Get the context menu items available for P(r).them allow fitting option
178            for Data2D and Data1D only.
179           
180            @param graph: the Graph object to which we attach the context menu
181            @return: a list of menu items with call-back function
182            @note: if Data1D was generated from Theory1D 
183                    the fitting option is not allowed
184        """
185        self.graph = graph
186        fit_option = "Select data for fitting"
187        fit_hint =  "Dialog with fitting parameters "
188       
189        for item in graph.plottables:
190            if item.__class__.__name__ is "Data2D":
191               
192                if hasattr(item,"is_data"):
193                    if item.is_data:
194                        return [[fit_option, fit_hint, self._onSelect]]
195                    else:
196                        return [] 
197                return [[fit_option, fit_hint, self._onSelect]]
198            else:
199                if item.name == graph.selected_plottable :
200                    if item.name in ["$I_{obs}(q)$","$I_{fit}(q)$","$P_{fit}(r)$"]:
201                        return [] 
202                    if hasattr(item, "group_id"):
203                        # if is_data is true , this in an actual data loaded
204                        #else it is a data created from a theory model
205                        if hasattr(item,"is_data"):
206                            if item.is_data:
207                                return [[fit_option, fit_hint,
208                                          self._onSelect]]
209                            else:
210                                return [] 
211                        else:
212                           return [[fit_option, fit_hint, self._onSelect]]
213        return []   
214
215
216    def get_panels(self, parent):
217        """
218            Create and return a list of panel objects
219        """
220        self.parent = parent
221        # Creation of the fit panel
222        self.fit_panel = FitPanel(self.parent, -1)
223        #Set the manager for the main panel
224        self.fit_panel.set_manager(self)
225        # List of windows used for the perspective
226        self.perspective = []
227        self.perspective.append(self.fit_panel.window_name)
228        # take care of saving  data, model and page associated with each other
229        self.page_finder = {}
230        #index number to create random model name
231        self.index_model = 0
232        self.index_theory= 0
233        self.parent.Bind(EVT_SLICER_PANEL, self._on_slicer_event)
234        self.parent.Bind(ERR_DATA, self._on_data_error)
235        self.parent.Bind(EVT_REMOVE_DATA, self._closed_fitpage)
236        self.parent.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
237        self.parent._mgr.Bind(wx.aui.EVT_AUI_PANE_CLOSE,self._onclearslicer)   
238
239       
240        #Send the fitting panel to guiframe
241        self.mypanels.append(self.fit_panel)
242       
243        return self.mypanels
244
245       
246     
247    def get_perspective(self):
248        """
249            Get the list of panel names for this perspective
250        """
251        return self.perspective
252   
253   
254    def on_perspective(self, event):
255        """
256            Call back function for the perspective menu item.
257            We notify the parent window that the perspective
258            has changed.
259        """
260        self.parent.set_perspective(self.perspective)
261   
262    def set_default_perspective(self):
263        """
264           Call back method that True to notify the parent that the current plug-in
265           can be set as default  perspective.
266           when returning False, the plug-in is not candidate for an automatic
267           default perspective setting
268        """
269        return True
270   
271    def post_init(self):
272        """
273            Post initialization call back to close the loose ends
274        """
275        # Do not show the fitting perspective immediately and
276        # let the application show the Welcome Page.
277        #self.parent.set_perspective(self.perspective)
278        pass
279
280    def copy_data(self, item, dy=None):
281        """
282            receive a data 1D and the list of errors on dy
283            and create a new data1D data
284            @param return
285        """
286        id = None
287        if hasattr(item,"id"):
288            id = item.id
289
290        data= Data1D(x=item.x, y=item.y,dx=None, dy=None)
291        data.copy_from_datainfo(item)
292        item.clone_without_data(clone=data)   
293        data.dy = deepcopy(dy)
294        data.name = item.name
295        ## allow to highlight data when plotted
296        data.interactive = deepcopy(item.interactive)
297        ## when 2 data have the same id override the 1 st plotted
298        data.id = id
299        data.group_id = item.group_id
300        return data
301   
302    def set_fit_range(self, page, qmin, qmax):
303        """
304            Set the fitting range of a given page
305        """
306        if page in self.page_finder.iterkeys():
307            fitproblem= self.page_finder[page]
308            fitproblem.set_range(qmin= qmin, qmax= qmax)
309                   
310    def schedule_for_fit(self,value=0,page=None,fitproblem =None): 
311        """
312            Set the fit problem field to 0 or 1 to schedule that problem to fit.
313            Schedule the specified fitproblem or get the fit problem related to
314            the current page and set value.
315            @param value : integer 0 or 1
316            @param fitproblem: fitproblem to schedule or not to fit
317        """   
318        if fitproblem !=None:
319            fitproblem.schedule_tofit(value)
320        else:
321            if page in self.page_finder.iterkeys():
322                fitproblem= self.page_finder[page]
323                fitproblem.schedule_tofit(value)
324         
325    def get_page_finder(self):
326        """ @return self.page_finder used also by simfitpage.py""" 
327        return self.page_finder
328   
329   
330    def set_page_finder(self,modelname,names,values):
331        """
332             Used by simfitpage.py to reset a parameter given the string constrainst.
333             @param modelname: the name ot the model for with the parameter has to reset
334             @param value: can be a string in this case.
335             @param names: the paramter name
336             @note: expecting park used for fit.
337        """ 
338        sim_page= self.sim_page
339        for page, value in self.page_finder.iteritems():
340            if page != sim_page:
341                list=value.get_model()
342                model = list[0]
343                if model.name== modelname:
344                    value.set_model_param(names,values)
345                    break
346         
347    def split_string(self,item): 
348        """
349            receive a word containing dot and split it. used to split parameterset
350            name into model name and parameter name example:
351            paramaterset (item) = M1.A
352            @return model_name =M1 , parameter name =A
353        """
354        if string.find(item,".")!=-1:
355            param_names= re.split("\.",item)
356            model_name=param_names[0]           
357            ##Assume max len is 3; eg., M0.radius.width
358            if len(param_names) == 3:
359                param_name=param_names[1]+"."+param_names[2]
360            else:
361                param_name=param_names[1]                   
362            return model_name,param_name
363       
364    def stop_fit(self):
365        """
366            Stop the fit engine
367        """
368        if self.calc_fit!= None and self.calc_fit.isrunning():
369            self.calc_fit.stop()
370            wx.PostEvent(self.parent, StatusEvent(status="Fitting  \
371                is cancelled" , type="stop"))
372           
373    def set_smearer(self,smearer, qmin=None, qmax=None):
374        """
375            Get a smear object and store it to a fit problem
376            @param smearer: smear object to allow smearing data
377        """   
378        self.current_pg=self.fit_panel.get_current_page()
379        self.page_finder[self.current_pg].set_smearer(smearer)
380        ## draw model 1D with smeared data
381        data =  self.page_finder[self.current_pg].get_fit_data()
382        model = self.page_finder[self.current_pg].get_model()
383        ## if user has already selected a model to plot
384        ## redraw the model with data smeared
385       
386        smear =self.page_finder[self.current_pg].get_smearer()
387        if model!= None:
388            self.draw_model( model=model, data= data, smearer= smear,
389                qmin= qmin, qmax= qmax)
390
391    def draw_model(self, model, data= None,smearer= None,
392                   enable1D= True, enable2D= False,
393                   qmin= DEFAULT_QMIN, qmax= DEFAULT_QMAX, qstep= DEFAULT_NPTS):
394        """
395             Draw model.
396             @param model: the model to draw
397             @param name: the name of the model to draw
398             @param data: the data on which the model is based to be drawn
399             @param description: model's description
400             @param enable1D: if true enable drawing model 1D
401             @param enable2D: if true enable drawing model 2D
402             @param qmin:  Range's minimum value to draw model
403             @param qmax:  Range's maximum value to draw model
404             @param qstep: number of step to divide the x and y-axis
405             
406        """
407        ## draw model 1D with no loaded data
408        self._draw_model1D( model= model, data= data,enable1D=enable1D, smearer= smearer,
409                           qmin= qmin, qmax= qmax, qstep= qstep )
410        ## draw model 2D with no initial data
411        self._draw_model2D(model=model,
412                           data = data,
413                           enable2D= enable2D,
414                           qmin=qmin,
415                           qmax=qmax,
416                           qstep=qstep)
417           
418    def onFit(self):
419        """
420            perform fit
421        """
422        ##  count the number of fitproblem schedule to fit
423        fitproblem_count= 0
424        for value in self.page_finder.itervalues():
425            if value.get_scheduled()==1:
426                fitproblem_count += 1
427               
428        ## if simultaneous fit change automatically the engine to park
429        if fitproblem_count >1:
430            self._on_change_engine(engine='park')
431           
432        self.fitproblem_count = fitproblem_count 
433         
434        from sans.fit.Fitting import Fit
435        self.fitter= Fit(self._fit_engine)
436       
437        if self._fit_engine=="park":
438            engineType="Simultaneous Fit"
439        else:
440            engineType="Single Fit"
441           
442        fproblemId = 0
443        self.current_pg=None
444        for page, value in self.page_finder.iteritems():
445            try:
446                if value.get_scheduled()==1:
447                    #Get list of parameters name to fit
448                    pars = []
449                    templist = []
450                    templist = page.get_param_list()
451                    for element in templist:
452                        name = str(element[1])
453                        pars.append(name)
454                    #Set Engine  (model , data) related to the page on
455                    self._fit_helper( value=value,pars=pars,
456                                      id=fproblemId, title= engineType ) 
457                    fproblemId += 1 
458                    self.current_pg= page
459            except:
460                msg= "%s error: %s" % (engineType,sys.exc_value)
461                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
462                                                      type="stop"))
463                return 
464        ## If a thread is already started, stop it
465        #if self.calc_fit!= None and self.calc_fit.isrunning():
466        #    self.calc_fit.stop()
467         #Handler used for park engine displayed message
468        handler = ConsoleUpdate(parent=self.parent,improvement_delta=0.1)
469        ## perform single fit
470        if fitproblem_count == 1:
471            calc_fit=FitThread(parent =self.parent,
472                                    handler = handler,
473                                    fn= self.fitter,
474                                   cpage=self.current_pg,
475                                   pars= pars,
476                                   updatefn=handler.update_fit,
477                                   completefn= self._single_fit_completed)
478        else:
479            ## Perform more than 1 fit at the time
480            calc_fit=FitThread(parent=self.parent,
481                                handler=handler,
482                                    fn= self.fitter,
483                                   completefn= self._simul_fit_completed,
484                                  updatefn=handler.update_fit)
485       
486        calc_fit.queue()
487        self.ready_fit(calc_fit=calc_fit)
488     
489    def ready_fit(self, calc_fit):
490        """
491        Ready for another fit
492        """
493        if self.fitproblem_count != None and self.fitproblem_count > 1:
494            calc_fit.ready(2.5)
495           
496        else:
497            time.sleep(0.4)
498           
499    def remove_plot(self, page, theory=False):
500        """
501            remove model plot when a fit page is closed
502        """
503        fitproblem = self.page_finder[page]
504        data = fitproblem.get_fit_data()
505        model = fitproblem.get_model()
506        if model is not None:
507            name = model.name
508            new_plot = Theory1D(x=[], y=[], dy=None)
509            new_plot.name = name
510            new_plot.xaxis(data._xaxis, data._xunit)
511            new_plot.yaxis(data._yaxis, data._yunit)
512            new_plot.group_id = data.group_id
513            new_plot.id = data.id + name
514            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=data.name))
515        if theory:
516            new_plot_data = Data1D(x=[], y=[], dx=None, dy=None)
517            new_plot_data.name = data.name
518            new_plot_data.xaxis(data._xaxis, data._xunit)
519            new_plot_data.yaxis(data._yaxis, data._yunit)
520            new_plot_data.group_id = data.group_id
521            new_plot_data.id = data.id
522            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot_data,
523                                                    title=data.name))
524    def create_fittable_data2D(self, data):
525        """
526            check if the current data is a data 2d and add dy to that data
527            @return Data2D
528        """
529        if data.__class__.__name__ != "Data2D":
530            raise ValueError, " create_fittable_data2D expects a Data2D"
531        ## Data2D case
532        new_data = deepcopy(data)
533        if not hasattr(data, "is_data"):
534            new_data.group_id += "data2D"
535            new_data.id +="data2D"
536            new_data.is_data = False
537            title = new_data.name
538            title += " Fit"
539            wx.PostEvent(self.parent, NewPlotEvent(plot=new_data,
540                                                    title=str(title)))
541        else:
542            new_data.is_data = True
543        return new_data
544       
545    def create_fittable_data1D(self, data):
546        """
547            check if the current data is a theory 1d and add dy to that data
548            @return Data1D
549        """
550        class_name = data.__class__.__name__
551        if not class_name in ["Data1D", "Theory1D"]:
552            raise ValueError, "create_fittable_data1D expects Data1D"
553     
554        #get the appropriate dy
555        dy = deepcopy(data.dy)
556        if len(self.err_dy) > 0:
557            if data.name in  self.err_dy.iterkeys():
558                dy = self.err_dy[data.name]   
559        if data.__class__.__name__ == "Theory1D":
560            new_data = self.copy_data(data, dy)
561            new_data.group_id = str(new_data.group_id)+"data1D"
562            new_data.id = str(new_data.id)+"data1D"
563            new_data.is_data = False
564            title = new_data.name
565            title = 'Data created from Theory'
566            wx.PostEvent(self.parent, NewPlotEvent(plot=new_data,
567                                                    title=str(title),
568                                                   reset=True))
569        else:
570            new_data = self.copy_data(data, dy) 
571            new_data.id = data.id
572            new_data.is_data = True
573        return new_data
574           
575    def add_fit_page(self, data):
576        """
577            given a data, ask to the fitting panel to create a new fitting page,
578            get this page and store it into the page_finder of this plug-in
579        """
580        try:
581            page = self.fit_panel.add_fit_page(data)
582            # add data associated to the page created
583            if page != None: 
584                page.set_data(data) 
585                #create a fitproblem storing all link to data,model,page creation
586                if not page in self.page_finder.keys():
587                    self.page_finder[page]= FitProblem()
588                ## item is almost the same as data but contains
589                ## axis info for plotting
590                #self.page_finder[page].add_plotted_data(item)
591                self.page_finder[page].add_fit_data(data)
592
593                wx.PostEvent(self.parent, StatusEvent(status="Page Created",
594                                                                info="info"))
595            else:
596                msg = "Page was already Created"
597                wx.PostEvent(self.parent, StatusEvent(status=msg, info="warning"))
598        except:
599            msg = "Creating Fit page: %s"%sys.exc_value
600            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
601            return
602   
603    def _onEVT_SLICER_PANEL(self, event):
604        """
605            receive and event telling to update a panel with a name starting with
606            event.panel_name. this method update slicer panel for a given interactor.
607            @param event: contains type of slicer , paramaters for updating the panel
608            and panel_name to find the slicer 's panel concerned.
609        """
610        for item in self.parent.panels:
611            if self.parent.panels[item].window_caption.startswith(event.panel_name):
612                self.parent.panels[item].set_slicer(event.type, event.params)
613               
614        self.parent._mgr.Update()
615   
616             
617    def _closed_fitpage(self, event):   
618        """
619            request fitpanel to close a given page when its unique data is removed
620            from the plot. close fitpage only when the a loaded data is removed
621        """   
622        if event is None or event.data is None:
623            return
624       
625        if hasattr(event.data,"is_data"):
626            if not event.data.is_data or event.data.__class__.__name__=="Data1D":
627                self.fit_panel.close_page_with_data(event.data) 
628       
629    def _add_page_onmenu(self, name,fitproblem=None):
630        """
631            Add name of a closed page of fitpanel in a menu
632        """
633        list = self.menu1.GetMenuItems()
634        for item in list:
635            if name == item.GetItemLabel():
636                self.closed_page_dict[name][1] = fitproblem
637               
638        if not name in self.closed_page_dict.keys():   
639            # Post paramters
640            event_id = wx.NewId()
641            self.menu1.Append(event_id, name, "Show %s fit panel" % name)
642            self.closed_page_dict[name]= [event_id, fitproblem]
643            wx.EVT_MENU(self.parent,event_id,  self._open_closed_page)
644       
645       
646    def _open_closed_page(self, event):   
647        """
648            reopen a closed page
649        """
650        for name, value in self.closed_page_dict.iteritems():
651            if event.GetId() in value:
652                id,fitproblem = value
653                if name !="Model":
654                    data= fitproblem.get_fit_data()
655                    page = self.fit_panel.add_fit_page(data= data,reset=True)
656                    if fitproblem != None:
657                        self.page_finder[page]=fitproblem
658                        if self.sim_page != None:
659                            self.sim_page.draw_page()
660                           
661                else:
662                    model = fitproblem
663                    self.fit_panel.add_model_page(model=model, topmenu=True,
664                                                  reset= True)
665                    break
666       
667       
668    def _reset_schedule_problem(self, value=0):
669        """
670             unschedule or schedule all fitproblem to be fit
671        """
672        for page, fitproblem in self.page_finder.iteritems():
673            fitproblem.schedule_tofit(value)
674           
675    def _fit_helper(self,pars,value, id, title="Single Fit " ):
676        """
677            helper for fitting
678        """
679        metadata = value.get_fit_data()
680        model = value.get_model()
681        smearer = value.get_smearer()
682        qmin , qmax = value.get_range()
683        self.fit_id =id
684        #Create list of parameters for fitting used
685        templist=[]
686       
687        try:
688            #Extra list of parameters and their constraints
689            listOfConstraint= []
690           
691            param = value.get_model_param()
692            if len(param)>0:
693                for item in param:
694                    ## check if constraint
695                    if item[0] !=None and item[1] != None:
696                        listOfConstraint.append((item[0],item[1]))
697                   
698            #Do the single fit
699            self.fitter.set_model(model, self.fit_id,
700                                   pars,constraints = listOfConstraint)
701           
702            self.fitter.set_data(data=metadata,Uid=self.fit_id,
703                                 smearer=smearer,qmin= qmin,qmax=qmax )
704           
705            self.fitter.select_problem_for_fit(Uid= self.fit_id,
706                                               value= value.get_scheduled())
707            value.clear_model_param()
708        except:
709            msg= title +" error: %s" % sys.exc_value
710            wx.PostEvent(self.parent, StatusEvent(status= msg, type="stop"))
711            return
712       
713    def _onSelect(self,event):
714        """
715            when Select data to fit a new page is created .Its reference is
716            added to self.page_finder
717        """
718        self.panel = event.GetEventObject()
719        Plugin.on_perspective(self,event=event)
720        for plottable in self.panel.graph.plottables:
721            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
722                if plottable.name == self.panel.graph.selected_plottable:
723                    data = self.create_fittable_data1D(data=plottable)
724                    self.add_fit_page(data=data)
725                    return
726            else:
727                data = self.create_fittable_data2D(data=plottable)
728                self.add_fit_page(data=data)
729           
730    def _single_fit_completed(self,result,pars,cpage, elapsed=None):
731        """
732            Display fit result on one page of the notebook.
733            @param result: result of fit
734            @param pars: list of names of parameters fitted
735            @param current_pg: the page where information will be displayed
736            @param qmin: the minimum value of x to replot the model
737            @param qmax: the maximum value of x to replot model
738         
739        """     
740        try:
741            if result ==None:
742                msg= "Simple Fitting Stop !!!"
743                wx.PostEvent(self.parent, StatusEvent(status=msg,info="warning",
744                                                      type="stop"))
745                return
746            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
747                msg= "Single Fitting did not converge!!!"
748                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
749                return
750            for page, value in self.page_finder.iteritems():
751                if page==cpage :
752                    model= value.get_model()
753                    break
754            param_name = []
755            i = 0
756            for name in pars:
757                param_name.append(name)
758
759            cpage.onsetValues(result.fitness,param_name, result.pvec,result.stderr)
760           
761        except:
762            msg= "Single Fit completed but Following error occurred:%s"% sys.exc_value
763            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
764                                                  type="stop"))
765            return
766       
767       
768    def _simul_fit_completed(self,result,pars=None,cpage=None, elapsed=None):
769        """
770            Parameter estimation completed,
771            display the results to the user
772            @param alpha: estimated best alpha
773            @param elapsed: computation time
774        """
775        ## fit more than 1 model at the same time
776        try:
777            msg = "" 
778            if result ==None:
779                msg= "Complex Fitting Stop !!!"
780                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
781                return
782            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
783                msg= "Single Fitting did not converge!!!"
784                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
785                return
786             
787            for page, value in self.page_finder.iteritems():
788                """
789                if format_number(result.fitness) == page.get_chi2():
790                    #ToDo: Compare parameter inputs with outputs too.
791                    wx.PostEvent(self.parent, StatusEvent(status="%s " % msg))
792                    break     
793                """             
794                if value.get_scheduled()==1:
795                    model = value.get_model()
796                    data =  value.get_fit_data()
797                    small_param_name = []
798                    small_out = []
799                    small_cov = []
800                    i = 0
801                    #Separate result in to data corresponding to each page
802                    for p in result.parameters:
803                        model_name,param_name = self.split_string(p.name) 
804                        if model.name == model_name:
805                            p_name= model.name+"."+param_name
806                            if p.name == p_name:     
807                                if p.value != None and numpy.isfinite(p.value):
808                                    small_out.append(p.value )
809                                    small_param_name.append(param_name)
810                                    small_cov.append(p.stderr)
811
812                    # Display result on each page
813                    page.onsetValues(result.fitness, small_param_name,small_out,small_cov)
814        except:
815             msg= "Simultaneous Fit completed"
816             msg +=" but Following error occurred:%s"%sys.exc_value
817             wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
818             return 
819             
820                           
821       
822    def _on_show_panel(self, event):
823        print "_on_show_panel: fitting"
824     
825     
826    def _onset_engine_park(self,event):
827        """
828            set engine to park
829        """
830        Plugin.on_perspective(self,event=event)
831        self._on_change_engine('park')
832       
833       
834    def _onset_engine_scipy(self,event):
835        """
836            set engine to scipy
837        """
838        self._on_change_engine('scipy')
839       
840    def _on_slicer_event(self, event):
841        """
842            Receive a panel as event and send it to guiframe
843            @param event: event containing a panel
844        """
845       
846        if event.panel!=None:
847            new_panel = event.panel
848            self.slicer_panels.append(event.panel)
849            # Set group ID if available
850            event_id = self.parent.popup_panel(new_panel)
851            #self.menu3.Append(event_id, new_panel.window_caption,
852            #                 "Show %s plot panel" % new_panel.window_caption)
853            # Set UID to allow us to reference the panel later
854         
855            new_panel.uid = event_id
856            self.mypanels.append(new_panel) 
857        return 
858   
859    def _onclearslicer(self, event):
860        """
861            Clear the boxslicer when close the panel associate with this slicer
862        """
863        name =event.GetPane().caption
864   
865        for panel in self.slicer_panels:
866            if panel.window_caption==name:
867               
868                for item in self.parent.panels:
869                    if hasattr(self.parent.panels[item],"uid"):
870                        if self.parent.panels[item].uid ==panel.base.uid:
871                            self.parent.panels[item].onClearSlicer(event)
872                            self.parent._mgr.Update()
873                            break 
874                break
875       
876       
877       
878       
879    def _return_engine_type(self):
880        """
881            return the current type of engine
882        """
883        return self._fit_engine
884     
885     
886    def _on_change_engine(self, engine='park'):
887        """
888            Allow to select the type of engine to perform fit
889            @param engine: the key work of the engine
890        """
891       
892        ## saving fit engine name
893        self._fit_engine = engine
894        ## change menu item state
895        if engine=="park":
896            self.menu1.FindItemByPosition(0).Check(False)
897            self.menu1.FindItemByPosition(1).Check(True)
898        else:
899            self.menu1.FindItemByPosition(0).Check(True)
900            self.menu1.FindItemByPosition(1).Check(False)
901           
902        ## post a message to status bar
903        wx.PostEvent(self.parent, StatusEvent(status="Engine set to: %s" % self._fit_engine))
904   
905        ## Bind every open fit page with a newevent to know the current fitting engine
906        import fitpage
907        event= fitpage.FitterTypeEvent()
908        event.type = self._fit_engine
909        for key in self.page_finder.keys():
910            wx.PostEvent(key, event)
911       
912   
913    def _on_model_panel(self, evt):
914        """
915            react to model selection on any combo box or model menu.plot the model 
916            @param evt: wx.combobox event
917        """
918        model = evt.model
919        if model == None:
920            return
921        smearer = None
922        qmin = None
923        qmax = None
924        if hasattr(evt, "qmin"):
925            qmin = evt.qmin
926        if hasattr(evt, "qmax"):
927            qmax = evt.qmax
928        if hasattr(evt, "smearer"):
929            smearer = evt.smearer
930        model.origin_name = model.name
931        self.current_pg = self.fit_panel.get_current_page() 
932        ## make sure nothing is done on self.sim_page
933        ## example trying to call set_panel on self.sim_page
934        if self.current_pg != self.sim_page :
935            if self.page_finder[self.current_pg].get_model()== None :
936               
937                model.name = "M"+str(self.index_model)
938                self.index_model += 1 
939            else:
940                model.name= self.page_finder[self.current_pg].get_model().name
941           
942            data = self.page_finder[self.current_pg].get_fit_data()
943           
944            # save the name containing the data name with the appropriate model
945            self.page_finder[self.current_pg].set_model(model)
946            qmin, qmax= self.current_pg.get_range()
947            self.page_finder[self.current_pg].set_range(qmin=qmin, qmax=qmax)
948            smearer=  self.page_finder[self.current_pg].get_smearer()
949            # save model name
950            self.set_smearer(smearer=smearer, qmin=qmin, qmax=qmax)
951           
952            if self.sim_page!=None:
953                self.sim_page.draw_page()
954       
955    def _on_model_menu(self, evt):
956        """
957            Plot a theory from a model selected from the menu
958            @param evt: wx.menu event
959        """
960        model = evt.model
961        Plugin.on_perspective(self,event=evt)
962        # Create a model page. If a new page is created, the model
963        # will be plotted automatically. If a page already exists,
964        # the content will be updated and the plot refreshed
965        self.fit_panel.add_model_page(model,topmenu=True)
966   
967   
968   
969   
970    def _update1D(self,x, output):
971        """
972            Update the output of plotting model 1D
973        """
974        wx.PostEvent(self.parent, StatusEvent(status="Plot \
975        #updating ... ",type="update"))
976        self.ready_fit()
977        #self.calc_thread.ready(0.01)
978   
979   
980    def _fill_default_model2D(self, theory, qmax,qstep, qmin=None):
981        """
982            fill Data2D with default value
983            @param theory: Data2D to fill
984        """
985        from DataLoader.data_info import Detector, Source
986       
987        detector = Detector()
988        theory.detector.append(detector)         
989        theory.source= Source()
990       
991        ## Default values   
992        theory.detector[0].distance= 8000   # mm       
993        theory.source.wavelength= 6         # A     
994        theory.detector[0].pixel_size.x= 5  # mm
995        theory.detector[0].pixel_size.y= 5  # mm
996       
997        theory.detector[0].beam_center.x= qmax
998        theory.detector[0].beam_center.y= qmax
999       
1000       
1001        ## create x_bins and y_bins of the model 2D
1002        pixel_width_x = theory.detector[0].pixel_size.x
1003        pixel_width_y = theory.detector[0].pixel_size.y
1004        center_x      = theory.detector[0].beam_center.x/pixel_width_x
1005        center_y      = theory.detector[0].beam_center.y/pixel_width_y
1006
1007        # theory default: assume the beam center is located at the center of sqr detector
1008        xmax = qmax
1009        xmin = -qmax
1010        ymax = qmax
1011        ymin = -qmax
1012       
1013        x=  numpy.linspace(start= -1*qmax,
1014                               stop= qmax,
1015                               num= qstep,
1016                               endpoint=True ) 
1017        y = numpy.linspace(start= -1*qmax,
1018                               stop= qmax,
1019                               num= qstep,
1020                               endpoint=True )
1021         
1022        ## use data info instead
1023        new_x = numpy.tile(x, (len(y),1))
1024        new_y = numpy.tile(y, (len(x),1))
1025        new_y = new_y.swapaxes(0,1)
1026       
1027        # all data reuire now in 1d array
1028        qx_data = new_x.flatten()
1029        qy_data = new_y.flatten()
1030       
1031        q_data = numpy.sqrt(qx_data*qx_data+qy_data*qy_data)
1032        # set all True (standing for unmasked) as default
1033        mask    = numpy.ones(len(qx_data), dtype = bool)
1034       
1035        # calculate the range of qx and qy: this way, it is a little more independent
1036        x_size = xmax- xmin
1037        y_size = ymax -ymin
1038       
1039        # store x and y bin centers in q space
1040        x_bins  = x
1041        y_bins  = y
1042        # bin size: x- & y-directions
1043        xstep = x_size/len(x_bins-1)
1044        ystep = y_size/len(y_bins-1)
1045       
1046        #theory.data = numpy.zeros(len(mask))
1047        theory.err_data = numpy.zeros(len(mask))
1048        theory.qx_data = qx_data
1049        theory.qy_data = qy_data 
1050        theory.q_data = q_data
1051        theory.mask = mask           
1052        theory.x_bins = x_bins 
1053        theory.y_bins = y_bins   
1054       
1055        # max and min taking account of the bin sizes
1056        theory.xmin= xmin
1057        theory.xmax= xmax
1058        theory.ymin= ymin
1059        theory.ymax= ymax
1060        theory.group_id ="Model"
1061        theory.id ="Model"
1062       
1063       
1064    def _get_plotting_info(self, data=None):
1065        """
1066            get plotting info from data if data !=None
1067            else use some default
1068        """
1069        my_info = PlotInfo()
1070        if data !=None:
1071            if hasattr(data,"info"):
1072                x_name, x_units = data.get_xaxis() 
1073                y_name, y_units = data.get_yaxis() 
1074               
1075                my_info._xunit = x_units
1076                my_info._xaxis = x_name
1077                my_info._yunit = y_units
1078                my_info._yaxis = y_name
1079               
1080            my_info.title= data.name
1081            if hasattr(data, "info"):
1082                my_info.info= data.info
1083            if hasattr(data, "group_id"):
1084                my_info.group_id= data.group_id
1085       
1086        return my_info
1087               
1088               
1089    def _complete1D(self, x,y, elapsed,model,data=None):
1090        """
1091            Complete plotting 1D data
1092        """ 
1093        try:
1094            new_plot = Theory1D(x=x, y=y)
1095            my_info = self._get_plotting_info( data)
1096            new_plot.name = model.name
1097            new_plot.id = my_info.id
1098            new_plot.group_id = my_info.group_id
1099           
1100            new_plot.xaxis( my_info._xaxis,  my_info._xunit)
1101            new_plot.yaxis( my_info._yaxis, my_info._yunit)
1102            if data!=None:
1103                if new_plot.id == data.id:
1104                    new_plot.id += "Model"
1105                new_plot.is_data =False 
1106           
1107            title= new_plot.name
1108            #new_plot.perspective = self.get_perspective()
1109            # Pass the reset flag to let the plotting event handler
1110            # know that we are replacing the whole plot
1111            if title== None:
1112                title = "Analytical model 1D "
1113            if data ==None:
1114                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1115                             title=str(title), reset=True))
1116            else:
1117                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,title= str(title)))
1118            msg = "Plot 1D  complete !"
1119            wx.PostEvent( self.parent, StatusEvent(status=msg , type="stop" ))
1120        except:
1121            msg= " Error occurred when drawing %s Model 1D: "%new_plot.name
1122            msg+= " %s"%sys.exc_value
1123            wx.PostEvent( self.parent, StatusEvent(status= msg, type="stop"))
1124            return 
1125                 
1126    def _update2D(self, output,time=None):
1127        """
1128            Update the output of plotting model
1129        """
1130        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1131        #updating ... ",type="update"))
1132        self.ready_fit()
1133        #self.calc_thread.ready(0.01)
1134       
1135       
1136    def _complete2D(self, image,data, model,  elapsed,qmin, qmax,qstep=DEFAULT_NPTS):
1137        """
1138            Complete get the result of modelthread and create model 2D
1139            that can be plot.
1140        """
1141        err_image = numpy.zeros(numpy.shape(image))
1142       
1143        theory= Data2D(image= image , err_image= err_image)
1144        theory.name= model.name
1145       
1146        if data ==None:
1147            self._fill_default_model2D(theory= theory, qmax=qmax,qstep=qstep, qmin= qmin)
1148       
1149        else:
1150            theory.id= data.id+"Model"
1151            theory.group_id= data.name+"Model"
1152            theory.x_bins= data.x_bins
1153            theory.y_bins= data.y_bins
1154            theory.detector= data.detector
1155            theory.source= data.source
1156            theory.is_data =False 
1157            theory.qx_data = data.qx_data
1158            theory.qy_data = data.qy_data
1159            theory.q_data = data.q_data
1160            theory.err_data = data.err_data
1161            theory.mask = data.mask
1162            ## plot boundaries
1163            theory.ymin= data.ymin
1164            theory.ymax= data.ymax
1165            theory.xmin= data.xmin
1166            theory.xmax= data.xmax
1167     
1168       
1169        ## plot
1170        wx.PostEvent(self.parent, NewPlotEvent(plot=theory,
1171                         title="Analytical model 2D ", reset=True ))
1172        msg = "Plot 2D complete !"
1173        wx.PostEvent( self.parent, StatusEvent( status= msg , type="stop" ))
1174         
1175    def _on_data_error(self, event):
1176        """
1177            receives and event from plotting plu-gins to store the data name and
1178            their errors of y coordinates for 1Data hide and show error
1179        """
1180        self.err_dy = event.err_dy
1181        #print "receiving error dy",self.err_dy
1182         
1183    def _draw_model2D(self,model,data=None,description=None, enable2D=False,
1184                      qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep=DEFAULT_NPTS):
1185        """
1186            draw model in 2D
1187            @param model: instance of the model to draw
1188            @param description: the description of the model
1189            @param enable2D: when True allows to draw model 2D
1190            @param qmin: the minimum value to  draw model 2D
1191            @param qmax: the maximum value to draw model 2D
1192            @param qstep: the number of division of Qx and Qy of the model to draw
1193           
1194        """
1195        x=  numpy.linspace(start= -1*qmax,
1196                               stop= qmax,
1197                               num= qstep,
1198                               endpoint=True ) 
1199        y = numpy.linspace(start= -1*qmax,
1200                               stop= qmax,
1201                               num= qstep,
1202                               endpoint=True )
1203        ## use data info instead
1204        if data !=None:
1205            ## check if data2D to plot
1206            if hasattr(data, "x_bins"):
1207                enable2D = True
1208                x= data.x_bins
1209                y= data.y_bins
1210               
1211        if not enable2D:
1212            return
1213        try:
1214            from model_thread import Calc2D
1215            ## If a thread is already started, stop it
1216            if self.calc_2D != None and self.calc_2D.isrunning():
1217                self.calc_2D.stop()
1218            self.calc_2D = Calc2D(  x= x,
1219                                    y= y,
1220                                    model= model, 
1221                                    data = data,
1222                                    qmin= qmin,
1223                                    qmax= qmax,
1224                                    qstep= qstep,
1225                                    completefn= self._complete2D,
1226                                    updatefn= self._update2D )
1227            self.calc_2D.queue()
1228           
1229        except:
1230            msg= " Error occurred when drawing %s Model 2D: "%model.name
1231            msg+= " %s"%sys.exc_value
1232            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1233            return 
1234   
1235    def _draw_model1D(self, model, data=None, smearer= None,
1236                qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep= DEFAULT_NPTS,enable1D= True):
1237        """
1238            Draw model 1D from loaded data1D
1239            @param data: loaded data
1240            @param model: the model to plot
1241        """
1242        x=  numpy.linspace(start= qmin,
1243                           stop= qmax,
1244                           num= qstep,
1245                           endpoint=True
1246                           )
1247        if data!=None:
1248            ## check for data2D
1249            if hasattr(data,"x_bins"):
1250                return
1251            x = data.x
1252            if qmin == DEFAULT_QMIN :
1253                qmin = min(data.x)
1254            if qmax == DEFAULT_QMAX:
1255                qmax = max(data.x) 
1256           
1257       
1258        if not enable1D:
1259            return
1260   
1261        try:
1262            from model_thread import Calc1D
1263            ## If a thread is already started, stop it
1264            if self.calc_1D!= None and self.calc_1D.isrunning():
1265                self.calc_1D.stop()
1266            self.calc_1D= Calc1D( x= x,
1267                                  data = data,
1268                                  model= model, 
1269                                  qmin = qmin,
1270                                  qmax = qmax,
1271                                  smearer = smearer,
1272                                  completefn = self._complete1D,
1273                                  updatefn = self._update1D  )
1274            self.calc_1D.queue()
1275           
1276        except:
1277            msg= " Error occurred when drawing %s Model 1D: "%model.name
1278            msg+= " %s"%sys.exc_value
1279            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1280            return 
1281           
1282
1283def profile(fn, *args, **kw):
1284    import cProfile, pstats, os
1285    global call_result
1286    def call():
1287        global call_result
1288        call_result = fn(*args, **kw)
1289    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1290    stats = pstats.Stats('profile.out')
1291    #stats.sort_stats('time')
1292    stats.sort_stats('calls')
1293    stats.print_stats()
1294    os.unlink('profile.out')
1295    return call_result
1296if __name__ == "__main__":
1297    i = Plugin()
1298   
1299   
1300   
1301   
Note: See TracBrowser for help on using the repository browser.