source: sasview/sansview/perspectives/fitting/fitting.py @ 90c9cdf

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 90c9cdf was e54d2c32, checked in by Gervaise Alina <gervyh@…>, 14 years ago

working on status bar display for fit

  • Property mode set to 100644
File size: 49.5 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
621        """   
622        self.fit_panel.close_page_with_data(event.data) 
623       
624    def _add_page_onmenu(self, name,fitproblem=None):
625        """
626            Add name of a closed page of fitpanel in a menu
627        """
628        list = self.menu1.GetMenuItems()
629        for item in list:
630            if name == item.GetItemLabel():
631                self.closed_page_dict[name][1] = fitproblem
632               
633        if not name in self.closed_page_dict.keys():   
634            # Post paramters
635            event_id = wx.NewId()
636            self.menu1.Append(event_id, name, "Show %s fit panel" % name)
637            self.closed_page_dict[name]= [event_id, fitproblem]
638            wx.EVT_MENU(self.parent,event_id,  self._open_closed_page)
639       
640       
641    def _open_closed_page(self, event):   
642        """
643            reopen a closed page
644        """
645        for name, value in self.closed_page_dict.iteritems():
646            if event.GetId() in value:
647                id,fitproblem = value
648                if name !="Model":
649                    data= fitproblem.get_fit_data()
650                    page = self.fit_panel.add_fit_page(data= data,reset=True)
651                    if fitproblem != None:
652                        self.page_finder[page]=fitproblem
653                        if self.sim_page != None:
654                            self.sim_page.draw_page()
655                           
656                else:
657                    model = fitproblem
658                    self.fit_panel.add_model_page(model=model, topmenu=True,
659                                                  reset= True)
660                    break
661       
662       
663    def _reset_schedule_problem(self, value=0):
664        """
665             unschedule or schedule all fitproblem to be fit
666        """
667        for page, fitproblem in self.page_finder.iteritems():
668            fitproblem.schedule_tofit(value)
669           
670    def _fit_helper(self,pars,value, id, title="Single Fit " ):
671        """
672            helper for fitting
673        """
674        metadata = value.get_fit_data()
675        model = value.get_model()
676        smearer = value.get_smearer()
677        qmin , qmax = value.get_range()
678        self.fit_id =id
679        #Create list of parameters for fitting used
680        templist=[]
681       
682        try:
683            #Extra list of parameters and their constraints
684            listOfConstraint= []
685           
686            param = value.get_model_param()
687            if len(param)>0:
688                for item in param:
689                    ## check if constraint
690                    if item[0] !=None and item[1] != None:
691                        listOfConstraint.append((item[0],item[1]))
692                   
693            #Do the single fit
694            self.fitter.set_model(model, self.fit_id,
695                                   pars,constraints = listOfConstraint)
696           
697            self.fitter.set_data(data=metadata,Uid=self.fit_id,
698                                 smearer=smearer,qmin= qmin,qmax=qmax )
699            #print "self.fitter.set_data"
700            self.fitter.select_problem_for_fit(Uid= self.fit_id,
701                                               value= value.get_scheduled())
702            value.clear_model_param()
703        except:
704            msg= title +" error: %s" % sys.exc_value
705            wx.PostEvent(self.parent, StatusEvent(status= msg, type="stop"))
706            return
707       
708    def _onSelect(self,event):
709        """
710            when Select data to fit a new page is created .Its reference is
711            added to self.page_finder
712        """
713        self.panel = event.GetEventObject()
714        Plugin.on_perspective(self,event=event)
715        for plottable in self.panel.graph.plottables:
716            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
717                if plottable.name == self.panel.graph.selected_plottable:
718                    data = self.create_fittable_data1D(data=plottable)
719                    self.add_fit_page(data=data)
720                    return
721            else:
722                data = self.create_fittable_data2D(data=plottable)
723                self.add_fit_page(data=data)
724           
725    def _single_fit_completed(self,result,pars,cpage, elapsed=None):
726        """
727            Display fit result on one page of the notebook.
728            @param result: result of fit
729            @param pars: list of names of parameters fitted
730            @param current_pg: the page where information will be displayed
731            @param qmin: the minimum value of x to replot the model
732            @param qmax: the maximum value of x to replot model
733         
734        """     
735        try:
736            if result ==None:
737                msg= "Simple Fitting Stop !!!"
738                wx.PostEvent(self.parent, StatusEvent(status=msg,info="warning",
739                                                      type="stop"))
740                return
741            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
742                msg= "Single Fitting did not converge!!!"
743                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
744                return
745            for page, value in self.page_finder.iteritems():
746                if page==cpage :
747                    model= value.get_model()
748                    break
749            param_name = []
750            i = 0
751            for name in pars:
752                param_name.append(name)
753
754            cpage.onsetValues(result.fitness,param_name, result.pvec,result.stderr)
755           
756        except:
757            msg= "Single Fit completed but Following error occurred:%s"% sys.exc_value
758            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
759                                                  type="stop"))
760            return
761       
762       
763    def _simul_fit_completed(self,result,pars=None,cpage=None, elapsed=None):
764        """
765            Parameter estimation completed,
766            display the results to the user
767            @param alpha: estimated best alpha
768            @param elapsed: computation time
769        """
770        ## fit more than 1 model at the same time
771        try:
772            msg = "" 
773            if result ==None:
774                msg= "Complex Fitting Stop !!!"
775                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
776                return
777            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
778                msg= "Single Fitting did not converge!!!"
779                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
780                return
781             
782            for page, value in self.page_finder.iteritems():
783                """
784                if format_number(result.fitness) == page.get_chi2():
785                    #ToDo: Compare parameter inputs with outputs too.
786                    wx.PostEvent(self.parent, StatusEvent(status="%s " % msg))
787                    break     
788                """             
789                if value.get_scheduled()==1:
790                    model = value.get_model()
791                    data =  value.get_fit_data()
792                    small_param_name = []
793                    small_out = []
794                    small_cov = []
795                    i = 0
796                    #Separate result in to data corresponding to each page
797                    for p in result.parameters:
798                        model_name,param_name = self.split_string(p.name) 
799                        if model.name == model_name:
800                            p_name= model.name+"."+param_name
801                            if p.name == p_name:     
802                                if p.value != None and numpy.isfinite(p.value):
803                                    small_out.append(p.value )
804                                    small_param_name.append(param_name)
805                                    small_cov.append(p.stderr)
806
807                    # Display result on each page
808                    page.onsetValues(result.fitness, small_param_name,small_out,small_cov)
809        except:
810             msg= "Simultaneous Fit completed"
811             msg +=" but Following error occurred:%s"%sys.exc_value
812             wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
813             return 
814             
815                           
816       
817    def _on_show_panel(self, event):
818        print "_on_show_panel: fitting"
819     
820     
821    def _onset_engine_park(self,event):
822        """
823            set engine to park
824        """
825        Plugin.on_perspective(self,event=event)
826        self._on_change_engine('park')
827       
828       
829    def _onset_engine_scipy(self,event):
830        """
831            set engine to scipy
832        """
833        self._on_change_engine('scipy')
834       
835    def _on_slicer_event(self, event):
836        """
837            Receive a panel as event and send it to guiframe
838            @param event: event containing a panel
839        """
840       
841        if event.panel!=None:
842            new_panel = event.panel
843            self.slicer_panels.append(event.panel)
844            # Set group ID if available
845            event_id = self.parent.popup_panel(new_panel)
846            #self.menu3.Append(event_id, new_panel.window_caption,
847            #                 "Show %s plot panel" % new_panel.window_caption)
848            # Set UID to allow us to reference the panel later
849         
850            new_panel.uid = event_id
851            self.mypanels.append(new_panel) 
852        return 
853   
854    def _onclearslicer(self, event):
855        """
856            Clear the boxslicer when close the panel associate with this slicer
857        """
858        name =event.GetPane().caption
859   
860        for panel in self.slicer_panels:
861            if panel.window_caption==name:
862               
863                for item in self.parent.panels:
864                    if hasattr(self.parent.panels[item],"uid"):
865                        if self.parent.panels[item].uid ==panel.base.uid:
866                            self.parent.panels[item].onClearSlicer(event)
867                            self.parent._mgr.Update()
868                            break 
869                break
870       
871       
872       
873       
874    def _return_engine_type(self):
875        """
876            return the current type of engine
877        """
878        return self._fit_engine
879     
880     
881    def _on_change_engine(self, engine='park'):
882        """
883            Allow to select the type of engine to perform fit
884            @param engine: the key work of the engine
885        """
886       
887        ## saving fit engine name
888        self._fit_engine = engine
889        ## change menu item state
890        if engine=="park":
891            self.menu1.FindItemByPosition(0).Check(False)
892            self.menu1.FindItemByPosition(1).Check(True)
893        else:
894            self.menu1.FindItemByPosition(0).Check(True)
895            self.menu1.FindItemByPosition(1).Check(False)
896           
897        ## post a message to status bar
898        wx.PostEvent(self.parent, StatusEvent(status="Engine set to: %s" % self._fit_engine))
899   
900        ## Bind every open fit page with a newevent to know the current fitting engine
901        import fitpage
902        event= fitpage.FitterTypeEvent()
903        event.type = self._fit_engine
904        for key in self.page_finder.keys():
905            wx.PostEvent(key, event)
906       
907   
908    def _on_model_panel(self, evt):
909        """
910            react to model selection on any combo box or model menu.plot the model 
911            @param evt: wx.combobox event
912        """
913        model = evt.model
914        if model == None:
915            return
916        smearer = None
917        qmin = None
918        qmax = None
919        if hasattr(evt, "qmin"):
920            qmin = evt.qmin
921        if hasattr(evt, "qmax"):
922            qmax = evt.qmax
923        if hasattr(evt, "smearer"):
924            smearer = evt.smearer
925        #model.origin_name = model.name
926        self.current_pg = self.fit_panel.get_current_page() 
927        ## make sure nothing is done on self.sim_page
928        ## example trying to call set_panel on self.sim_page
929        if self.current_pg != self.sim_page :
930            if self.page_finder[self.current_pg].get_model()== None :
931               
932                model.name = "M"+str(self.index_model)
933                self.index_model += 1 
934            else:
935                model.name= self.page_finder[self.current_pg].get_model().name
936           
937            data = self.page_finder[self.current_pg].get_fit_data()
938           
939            # save the name containing the data name with the appropriate model
940            self.page_finder[self.current_pg].set_model(model)
941            qmin, qmax= self.current_pg.get_range()
942            self.page_finder[self.current_pg].set_range(qmin=qmin, qmax=qmax)
943            smearer=  self.page_finder[self.current_pg].get_smearer()
944            # save model name
945            self.set_smearer(smearer=smearer, qmin=qmin, qmax=qmax)
946           
947            if self.sim_page!=None:
948                self.sim_page.draw_page()
949       
950    def _on_model_menu(self, evt):
951        """
952            Plot a theory from a model selected from the menu
953            @param evt: wx.menu event
954        """
955        model = evt.model
956        Plugin.on_perspective(self,event=evt)
957        # Create a model page. If a new page is created, the model
958        # will be plotted automatically. If a page already exists,
959        # the content will be updated and the plot refreshed
960        self.fit_panel.add_model_page(model,topmenu=True)
961   
962   
963   
964   
965    def _update1D(self,x, output):
966        """
967            Update the output of plotting model 1D
968        """
969        wx.PostEvent(self.parent, StatusEvent(status="Plot \
970        #updating ... ",type="update"))
971        self.ready_fit()
972        #self.calc_thread.ready(0.01)
973   
974   
975    def _fill_default_model2D(self, theory, qmax,qstep, qmin=None):
976        """
977            fill Data2D with default value
978            @param theory: Data2D to fill
979        """
980        from DataLoader.data_info import Detector, Source
981       
982        detector = Detector()
983        theory.detector.append(detector)         
984        theory.source= Source()
985       
986        ## Default values   
987        theory.detector[0].distance= 8000   # mm       
988        theory.source.wavelength= 6         # A     
989        theory.detector[0].pixel_size.x= 5  # mm
990        theory.detector[0].pixel_size.y= 5  # mm
991       
992        theory.detector[0].beam_center.x= qmax
993        theory.detector[0].beam_center.y= qmax
994       
995       
996        ## create x_bins and y_bins of the model 2D
997        pixel_width_x = theory.detector[0].pixel_size.x
998        pixel_width_y = theory.detector[0].pixel_size.y
999        center_x      = theory.detector[0].beam_center.x/pixel_width_x
1000        center_y      = theory.detector[0].beam_center.y/pixel_width_y
1001
1002        # theory default: assume the beam center is located at the center of sqr detector
1003        xmax = qmax
1004        xmin = -qmax
1005        ymax = qmax
1006        ymin = -qmax
1007       
1008        x=  numpy.linspace(start= -1*qmax,
1009                               stop= qmax,
1010                               num= qstep,
1011                               endpoint=True ) 
1012        y = numpy.linspace(start= -1*qmax,
1013                               stop= qmax,
1014                               num= qstep,
1015                               endpoint=True )
1016         
1017        ## use data info instead
1018        new_x = numpy.tile(x, (len(y),1))
1019        new_y = numpy.tile(y, (len(x),1))
1020        new_y = new_y.swapaxes(0,1)
1021       
1022        # all data reuire now in 1d array
1023        qx_data = new_x.flatten()
1024        qy_data = new_y.flatten()
1025       
1026        q_data = numpy.sqrt(qx_data*qx_data+qy_data*qy_data)
1027        # set all True (standing for unmasked) as default
1028        mask    = numpy.ones(len(qx_data), dtype = bool)
1029       
1030        # calculate the range of qx and qy: this way, it is a little more independent
1031        x_size = xmax- xmin
1032        y_size = ymax -ymin
1033       
1034        # store x and y bin centers in q space
1035        x_bins  = x
1036        y_bins  = y
1037        # bin size: x- & y-directions
1038        xstep = x_size/len(x_bins-1)
1039        ystep = y_size/len(y_bins-1)
1040       
1041        #theory.data = numpy.zeros(len(mask))
1042        theory.err_data = numpy.zeros(len(mask))
1043        theory.qx_data = qx_data
1044        theory.qy_data = qy_data 
1045        theory.q_data = q_data
1046        theory.mask = mask           
1047        theory.x_bins = x_bins 
1048        theory.y_bins = y_bins   
1049       
1050        # max and min taking account of the bin sizes
1051        theory.xmin= xmin
1052        theory.xmax= xmax
1053        theory.ymin= ymin
1054        theory.ymax= ymax
1055        theory.group_id ="Model"
1056        theory.id ="Model"
1057       
1058       
1059    def _get_plotting_info(self, data=None):
1060        """
1061            get plotting info from data if data !=None
1062            else use some default
1063        """
1064        my_info = PlotInfo()
1065        if data !=None:
1066            if hasattr(data,"info"):
1067                x_name, x_units = data.get_xaxis() 
1068                y_name, y_units = data.get_yaxis() 
1069               
1070                my_info._xunit = x_units
1071                my_info._xaxis = x_name
1072                my_info._yunit = y_units
1073                my_info._yaxis = y_name
1074               
1075            my_info.title= data.name
1076            if hasattr(data, "info"):
1077                my_info.info= data.info
1078            if hasattr(data, "group_id"):
1079                my_info.group_id= data.group_id
1080       
1081        return my_info
1082               
1083               
1084    def _complete1D(self, x,y, elapsed,model,data=None):
1085        """
1086            Complete plotting 1D data
1087        """ 
1088        try:
1089            new_plot = Theory1D(x=x, y=y)
1090            my_info = self._get_plotting_info( data)
1091            new_plot.name = model.name
1092            new_plot.id = my_info.id
1093            new_plot.group_id = my_info.group_id
1094           
1095            new_plot.xaxis( my_info._xaxis,  my_info._xunit)
1096            new_plot.yaxis( my_info._yaxis, my_info._yunit)
1097            if data!=None:
1098                if new_plot.id == data.id:
1099                    new_plot.id += "Model"
1100                new_plot.is_data =False 
1101           
1102            title= new_plot.name
1103            #new_plot.perspective = self.get_perspective()
1104            # Pass the reset flag to let the plotting event handler
1105            # know that we are replacing the whole plot
1106            if title== None:
1107                title = "Analytical model 1D "
1108            if data ==None:
1109                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1110                             title=str(title), reset=True))
1111            else:
1112                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,title= str(title)))
1113            msg = "Plot 1D  complete !"
1114            wx.PostEvent( self.parent, StatusEvent(status=msg , type="stop" ))
1115        except:
1116            msg= " Error occurred when drawing %s Model 1D: "%new_plot.name
1117            msg+= " %s"%sys.exc_value
1118            wx.PostEvent( self.parent, StatusEvent(status= msg, type="stop"))
1119            return 
1120                 
1121    def _update2D(self, output,time=None):
1122        """
1123            Update the output of plotting model
1124        """
1125        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1126        #updating ... ",type="update"))
1127        self.ready_fit()
1128        #self.calc_thread.ready(0.01)
1129       
1130       
1131    def _complete2D(self, image,data, model,  elapsed,qmin, qmax,qstep=DEFAULT_NPTS):
1132        """
1133            Complete get the result of modelthread and create model 2D
1134            that can be plot.
1135        """
1136        err_image = numpy.zeros(numpy.shape(image))
1137       
1138        theory= Data2D(image= image , err_image= err_image)
1139        theory.name= model.name
1140       
1141        if data ==None:
1142            self._fill_default_model2D(theory= theory, qmax=qmax,qstep=qstep, qmin= qmin)
1143       
1144        else:
1145            theory.id= data.id+"Model"
1146            theory.group_id= data.name+"Model"
1147            theory.x_bins= data.x_bins
1148            theory.y_bins= data.y_bins
1149            theory.detector= data.detector
1150            theory.source= data.source
1151            theory.is_data =False 
1152            theory.qx_data = data.qx_data
1153            theory.qy_data = data.qy_data
1154            theory.q_data = data.q_data
1155            theory.err_data = data.err_data
1156            theory.mask = data.mask
1157            ## plot boundaries
1158            theory.ymin= data.ymin
1159            theory.ymax= data.ymax
1160            theory.xmin= data.xmin
1161            theory.xmax= data.xmax
1162     
1163       
1164        ## plot
1165        wx.PostEvent(self.parent, NewPlotEvent(plot=theory,
1166                         title="Analytical model 2D ", reset=True ))
1167        msg = "Plot 2D complete !"
1168        wx.PostEvent( self.parent, StatusEvent( status= msg , type="stop" ))
1169         
1170    def _on_data_error(self, event):
1171        """
1172            receives and event from plotting plu-gins to store the data name and
1173            their errors of y coordinates for 1Data hide and show error
1174        """
1175        self.err_dy = event.err_dy
1176        #print "receiving error dy",self.err_dy
1177         
1178    def _draw_model2D(self,model,data=None,description=None, enable2D=False,
1179                      qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep=DEFAULT_NPTS):
1180        """
1181            draw model in 2D
1182            @param model: instance of the model to draw
1183            @param description: the description of the model
1184            @param enable2D: when True allows to draw model 2D
1185            @param qmin: the minimum value to  draw model 2D
1186            @param qmax: the maximum value to draw model 2D
1187            @param qstep: the number of division of Qx and Qy of the model to draw
1188           
1189        """
1190        x=  numpy.linspace(start= -1*qmax,
1191                               stop= qmax,
1192                               num= qstep,
1193                               endpoint=True ) 
1194        y = numpy.linspace(start= -1*qmax,
1195                               stop= qmax,
1196                               num= qstep,
1197                               endpoint=True )
1198        ## use data info instead
1199        if data !=None:
1200            ## check if data2D to plot
1201            if hasattr(data, "x_bins"):
1202                enable2D = True
1203                x= data.x_bins
1204                y= data.y_bins
1205               
1206        if not enable2D:
1207            return
1208        try:
1209            from model_thread import Calc2D
1210            ## If a thread is already started, stop it
1211            if self.calc_2D != None and self.calc_2D.isrunning():
1212                self.calc_2D.stop()
1213            self.calc_2D = Calc2D(  x= x,
1214                                    y= y,
1215                                    model= model, 
1216                                    data = data,
1217                                    qmin= qmin,
1218                                    qmax= qmax,
1219                                    qstep= qstep,
1220                                    completefn= self._complete2D,
1221                                    updatefn= self._update2D )
1222            self.calc_2D.queue()
1223           
1224        except:
1225            msg= " Error occurred when drawing %s Model 2D: "%model.name
1226            msg+= " %s"%sys.exc_value
1227            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1228            return 
1229   
1230    def _draw_model1D(self, model, data=None, smearer= None,
1231                qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep= DEFAULT_NPTS,enable1D= True):
1232        """
1233            Draw model 1D from loaded data1D
1234            @param data: loaded data
1235            @param model: the model to plot
1236        """
1237        x=  numpy.linspace(start= qmin,
1238                           stop= qmax,
1239                           num= qstep,
1240                           endpoint=True
1241                           )
1242        if data!=None:
1243            ## check for data2D
1244            if hasattr(data,"x_bins"):
1245                return
1246            x = data.x
1247            if qmin == DEFAULT_QMIN :
1248                qmin = min(data.x)
1249            if qmax == DEFAULT_QMAX:
1250                qmax = max(data.x) 
1251           
1252       
1253        if not enable1D:
1254            return
1255   
1256        try:
1257            from model_thread import Calc1D
1258            ## If a thread is already started, stop it
1259            if self.calc_1D!= None and self.calc_1D.isrunning():
1260                self.calc_1D.stop()
1261            self.calc_1D= Calc1D( x= x,
1262                                  data = data,
1263                                  model= model, 
1264                                  qmin = qmin,
1265                                  qmax = qmax,
1266                                  smearer = smearer,
1267                                  completefn = self._complete1D,
1268                                  updatefn = self._update1D  )
1269            self.calc_1D.queue()
1270           
1271        except:
1272            msg= " Error occurred when drawing %s Model 1D: "%model.name
1273            msg+= " %s"%sys.exc_value
1274            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1275            return 
1276           
1277
1278def profile(fn, *args, **kw):
1279    import cProfile, pstats, os
1280    global call_result
1281    def call():
1282        global call_result
1283        call_result = fn(*args, **kw)
1284    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1285    stats = pstats.Stats('profile.out')
1286    #stats.sort_stats('time')
1287    stats.sort_stats('calls')
1288    stats.print_stats()
1289    os.unlink('profile.out')
1290    return call_result
1291if __name__ == "__main__":
1292    i = Plugin()
1293   
1294   
1295   
1296   
Note: See TracBrowser for help on using the repository browser.