source: sasview/src/sas/sasgui/perspectives/fitting/fitpanel.py @ 96b5f8de

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 96b5f8de was f60251f, checked in by krzywon, 9 years ago

Resolves #434 - Display the active fit optimizer on the fit panel.

  • Property mode set to 100644
File size: 22.7 KB
Line 
1"""
2FitPanel class contains fields allowing to fit  models and  data
3
4:note: For Fit to be performed the user should check at least one parameter
5    on fit Panel window.
6
7"""
8import wx
9from wx.aui import AuiNotebook as nb
10
11from sas.sasgui.guiframe.panel_base import PanelBase
12from sas.sasgui.guiframe.events import PanelOnFocusEvent
13from sas.sasgui.guiframe.events import StatusEvent
14from sas.sasgui.guiframe.dataFitting import check_data_validity
15
16import basepage
17import models
18_BOX_WIDTH = 80
19
20
21class FitPanel(nb, PanelBase):
22    """
23    FitPanel class contains fields allowing to fit  models and  data
24
25    :note: For Fit to be performed the user should check at least one parameter
26        on fit Panel window.
27
28    """
29    ## Internal name for the AUI manager
30    window_name = "Fit panel"
31    ## Title to appear on top of the window
32    window_caption = "Fit Panel "
33    CENTER_PANE = True
34
35    def __init__(self, parent, manager=None, *args, **kwargs):
36        """
37        """
38        nb.__init__(self, parent, wx.ID_ANY,
39                    style=wx.aui.AUI_NB_WINDOWLIST_BUTTON |
40                    wx.aui.AUI_NB_DEFAULT_STYLE |
41                    wx.CLIP_CHILDREN)
42        PanelBase.__init__(self, parent)
43        #self.SetWindowStyleFlag(style=nb.FNB_FANCY_TABS)
44        self._manager = manager
45        self.parent = parent
46        self.event_owner = None
47        #dictionary of miodel {model class name, model class}
48        self.menu_mng = models.ModelManager()
49        self.model_list_box = self.menu_mng.get_model_list()
50        #pageClosedEvent = nb.EVT_FLATNOTEBOOK_PAGE_CLOSING
51        self.model_dictionary = self.menu_mng.get_model_dictionary()
52        self.pageClosedEvent = wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE
53
54        self.Bind(self.pageClosedEvent, self.on_close_page)
55        ## save the title of the last page tab added
56        self.fit_page_name = {}
57        ## list of existing fit page
58        self.opened_pages = {}
59        #index of fit page
60        self.fit_page_index = 0
61        #index of batch page
62        self.batch_page_index = 0
63        #page of simultaneous fit
64        self.sim_page = None
65        self.batch_page = None
66        ## get the state of a page
67        self.Bind(basepage.EVT_PAGE_INFO, self._onGetstate)
68        self.Bind(basepage.EVT_PREVIOUS_STATE, self._onUndo)
69        self.Bind(basepage.EVT_NEXT_STATE, self._onRedo)
70        self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_page_changing)
71        self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSED, self.on_closed)
72
73    def on_closed(self, event):
74        """
75        """
76        if self.GetPageCount() == 0:
77            self.add_empty_page()
78            self.enable_close_button()
79
80    def save_project(self, doc=None):
81        """
82        return an xml node containing state of the panel
83         that guiframe can write to file
84        """
85        msg = ""
86        for uid, page in self.opened_pages.iteritems():
87            if page.batch_on:
88                pos = self.GetPageIndex(page)
89                if pos != -1 and page not in [self.sim_page, self.batch_page]:
90                    msg += "%s .\n" % str(self.GetPageText(pos))
91            else:
92                data = page.get_data()
93                # state must be cloned
94                state = page.get_state().clone()
95                if data is not None and page.model is not None:
96                    new_doc = self._manager.state_reader.write_toXML(data,
97                                                                     state)
98                    if doc != None and hasattr(doc, "firstChild"):
99                        child = new_doc.firstChild.firstChild
100                        doc.firstChild.appendChild(child)
101                    else:
102                        doc = new_doc
103        if msg.strip() != "":
104            temp = "Save Project is not supported for Batch page.\n"
105            temp += "The following pages will not be save:\n"
106            message = temp + msg
107            wx.PostEvent(self._manager.parent, StatusEvent(status=message,
108                                                            info="warning"))
109        return doc
110
111    def update_model_list(self):
112        """
113        """
114        temp = self.menu_mng.update()
115        if len(temp):
116            self.model_list_box = temp
117        return temp
118
119    def reset_pmodel_list(self):
120        """
121        """
122        temp = self.menu_mng.plugins_reset()
123        if len(temp):
124            self.model_list_box = temp
125        return temp
126
127    def get_page_by_id(self, uid):
128        """
129        """
130        if uid not in self.opened_pages:
131            msg = "Fitpanel cannot find ID: %s in self.opened_pages" % str(uid)
132            raise ValueError, msg
133        else:
134            return self.opened_pages[uid]
135
136    def on_page_changing(self, event):
137        """
138        calls the function when the current event handler has exited. avoiding
139        to call panel on focus on a panel that is currently deleted
140        """
141        wx.CallAfter(self.helper_on_page_change)
142
143    def helper_on_page_change(self):
144        """
145        """
146        pos = self.GetSelection()
147        if pos != -1:
148            selected_page = self.GetPage(pos)
149            wx.PostEvent(self._manager.parent,
150                         PanelOnFocusEvent(panel=selected_page))
151        self.enable_close_button()
152
153    def on_set_focus(self, event):
154        """
155        """
156        from bumps.options import FIT_CONFIG
157        current = FIT_CONFIG.selected_name
158        self.parent.SetTitle(self.window_name + " - Active Fitting Optimizer: " + current)
159        pos = self.GetSelection()
160        if pos != -1:
161            selected_page = self.GetPage(pos)
162            wx.PostEvent(self._manager.parent,
163                         PanelOnFocusEvent(panel=selected_page))
164
165    def get_data(self):
166        """
167        get the data in the current page
168        """
169        pos = self.GetSelection()
170        if pos != -1:
171            selected_page = self.GetPage(pos)
172            return selected_page.get_data()
173
174    def set_model_state(self, state):
175        """
176        receive a state to reset the model in the current page
177        """
178        pos = self.GetSelection()
179        if pos != -1:
180            selected_page = self.GetPage(pos)
181            selected_page.set_model_state(state)
182
183    def get_state(self):
184        """
185         return the state of the current selected page
186        """
187        pos = self.GetSelection()
188        if pos != -1:
189            selected_page = self.GetPage(pos)
190            return selected_page.get_state()
191
192    def close_all(self):
193        """
194        remove all pages, used when a svs file is opened
195        """
196
197        #get number of pages
198        nop = self.GetPageCount()
199        #use while-loop, for-loop will not do the job well.
200        while (nop > 0):
201            #delete the first page until no page exists
202            page = self.GetPage(0)
203            if self._manager.parent.panel_on_focus == page:
204                self._manager.parent.panel_on_focus = None
205            self._close_helper(selected_page=page)
206            self.DeletePage(0)
207            nop = nop - 1
208
209        ## save the title of the last page tab added
210        self.fit_page_name = {}
211        ## list of existing fit page
212        self.opened_pages = {}
213
214    def set_state(self, state):
215        """
216        Restore state of the panel
217        """
218        page_is_opened = False
219        if state is not None:
220            for uid, panel in self.opened_pages.iteritems():
221                #Don't return any panel is the exact same page is created
222                if uid == panel.uid and panel.data == state.data:
223                    # the page is still opened
224                    panel.reset_page(state=state)
225                    panel.save_current_state()
226                    page_is_opened = True
227            if not page_is_opened:
228                if state.data.__class__.__name__ != 'list':
229                    #To support older state file format
230                    list_data = [state.data]
231                else:
232                    #Todo: need new file format for the list
233                    list_data = state.data
234                panel = self._manager.add_fit_page(data=list_data)
235                # add data associated to the page created
236                if panel is not None:
237                    self._manager.store_data(uid=panel.uid,
238                                             data_list=list_data,
239                                             caption=panel.window_caption)
240                    panel.reset_page(state=state)
241                    panel.save_current_state()
242
243    def clear_panel(self):
244        """
245        Clear and close all panels, used by guimanager
246        """
247
248        #close all panels only when svs file opened
249        self.close_all()
250        self._manager.mypanels = []
251
252    def on_close_page(self, event=None):
253        """
254        close page and remove all references to the closed page
255        """
256        nbr_page = self.GetPageCount()
257        selected_page = self.GetPage(self.GetSelection())
258        if nbr_page == 1:
259            if selected_page.get_data() == None:
260                if event is not None:
261                    event.Veto()
262                return
263        self._close_helper(selected_page=selected_page)
264
265    def close_page_with_data(self, deleted_data):
266        """
267        close a fit page when its data is completely remove from the graph
268        """
269        if deleted_data is None:
270            return
271        for index in range(self.GetPageCount()):
272            selected_page = self.GetPage(index)
273            if hasattr(selected_page, "get_data"):
274                data = selected_page.get_data()
275
276                if data is None:
277                    #the fitpanel exists and only the initial fit page is open
278                    #with no selected data
279                    return
280                if data.id == deleted_data.id:
281                    self._close_helper(selected_page)
282                    self.DeletePage(index)
283                    break
284
285    def set_manager(self, manager):
286        """
287        set panel manager
288
289        :param manager: instance of plugin fitting
290
291        """
292        self._manager = manager
293        for pos in range(self.GetPageCount()):
294            page = self.GetPage(pos)
295            if page is not None:
296                page.set_manager(self._manager)
297
298    def set_model_list(self, dict):
299        """
300        copy a dictionary of model into its own dictionary
301
302        :param m_dict: dictionnary made of model name as key and model class
303            as value
304        """
305        self.model_list_box = dict
306
307    def set_model_dict(self, m_dict):
308        """
309        copy a dictionary of model name -> model object
310
311        :param m_dict: dictionary linking model name -> model object
312        """
313
314    def get_current_page(self):
315        """
316        :return: the current page selected
317
318        """
319        return self.GetPage(self.GetSelection())
320
321    def add_sim_page(self, caption="Const & Simul Fit"):
322        """
323        Add the simultaneous fit page
324        """
325        from simfitpage import SimultaneousFitPage
326        page_finder = self._manager.get_page_finder()
327        if caption == "Const & Simul Fit":
328            self.sim_page = SimultaneousFitPage(self, page_finder=page_finder,
329                                                 id= wx.ID_ANY, batch_on=False)
330            self.sim_page.window_caption = caption
331            self.sim_page.window_name = caption
332            self.sim_page.uid = wx.NewId()
333            self.AddPage(self.sim_page, caption, True)
334            self.sim_page.set_manager(self._manager)
335            self.enable_close_button()
336            return self.sim_page
337        else:
338            self.batch_page = SimultaneousFitPage(self, batch_on=True,
339                                                   page_finder=page_finder)
340            self.batch_page.window_caption = caption
341            self.batch_page.window_name = caption
342            self.batch_page.uid = wx.NewId()
343            self.AddPage(self.batch_page, caption, True)
344            self.batch_page.set_manager(self._manager)
345            self.enable_close_button()
346            return self.batch_page
347
348    def add_empty_page(self):
349        """
350        add an empty page
351        """
352        """
353        if self.batch_on:
354            from batchfitpage import BatchFitPage
355            panel = BatchFitPage(parent=self)
356            #Increment index of batch page
357            self.batch_page_index += 1
358            index = self.batch_page_index
359        else:
360        """
361        from fitpage import FitPage
362        from batchfitpage import BatchFitPage
363        if self.batch_on:
364            panel = BatchFitPage(parent=self)
365            self.batch_page_index += 1
366            caption = "BatchPage" + str(self.batch_page_index)
367            panel.set_index_model(self.batch_page_index)
368        else:
369            #Increment index of fit page
370            panel = FitPage(parent=self)
371            self.fit_page_index += 1
372            caption = "FitPage" + str(self.fit_page_index)
373            panel.set_index_model(self.fit_page_index)
374        panel.batch_on = self.batch_on
375        panel._set_save_flag(not panel.batch_on)
376        panel.set_model_dictionary(self.model_dictionary)
377        panel.populate_box(model_dict=self.model_list_box)
378        panel.formfactor_combo_init()
379        panel.set_manager(self._manager)
380        panel.window_caption = caption
381        panel.window_name = caption
382        self.AddPage(panel, caption, select=True)
383        self.opened_pages[panel.uid] = panel
384        self._manager.create_fit_problem(panel.uid)
385        self._manager.page_finder[panel.uid].add_data(panel.get_data())
386        self.enable_close_button()
387        panel.on_set_focus(None)
388        return panel
389
390    def enable_close_button(self):
391        """
392        display the close button on tab for more than 1 tabs else remove the
393        close button
394        """
395        if self.GetPageCount() <= 1:
396            style = self.GetWindowStyleFlag()
397            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
398            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB == flag:
399                style = style & ~wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
400                self.SetWindowStyle(style)
401        else:
402            style = self.GetWindowStyleFlag()
403            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
404            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB != flag:
405                style |= wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
406                self.SetWindowStyle(style)
407
408    def delete_data(self, data):
409        """
410        Delete the given data
411        """
412        if data.__class__.__name__ != "list":
413            raise ValueError, "Fitpanel delete_data expect list of id"
414        else:
415            n = self.GetPageCount()
416            for page in self.opened_pages.values():
417                pos = self.GetPageIndex(page)
418                temp_data = page.get_data()
419                if temp_data is not None and temp_data.id in data:
420                    self.SetSelection(pos)
421                    self.on_close_page(event=None)
422                    temp = self.GetSelection()
423                    self.DeletePage(temp)
424            if self.GetPageCount() == 0:
425                self._manager.on_add_new_page(event=None)
426
427    def set_data_on_batch_mode(self, data_list):
428        """
429        Add all data to a single tab when the application is on Batch mode.
430        However all data in the set of data must be either 1D or 2D type.
431        This method presents option to select the data type before creating a
432        tab.
433        """
434        data_1d_list = []
435        data_2d_list = []
436        group_id_1d = wx.NewId()
437        # separate data into data1d and data2d list
438        for data in data_list:
439            if data.__class__.__name__ == "Data1D":
440                data.group_id = group_id_1d
441                data_1d_list.append(data)
442            if data.__class__.__name__ == "Data2D":
443                data.group_id = wx.NewId()
444                data_2d_list.append(data)
445        page = None
446        for p in self.opened_pages.values():
447            #check if there is an empty page to fill up
448            if not check_data_validity(p.get_data()) and p.batch_on:
449
450                #make sure data get placed in 1D empty tab if data is 1D
451                #else data get place on 2D tab empty tab
452                enable2D = p.get_view_mode()
453                if (data.__class__.__name__ == "Data2D" and enable2D)\
454                or (data.__class__.__name__ == "Data1D" and not enable2D):
455                    page = p
456                    break
457        if data_1d_list and data_2d_list:
458            # need to warning the user that this batch is a special case
459            from sas.sasgui.perspectives.fitting.fitting_widgets import BatchDataDialog
460            dlg = BatchDataDialog(self)
461            if dlg.ShowModal() == wx.ID_OK:
462                data_type = dlg.get_data()
463                dlg.Destroy()
464                if page  is None:
465                    page = self.add_empty_page()
466                if data_type == 1:
467                    #user has selected only data1D
468                    page.fill_data_combobox(data_1d_list)
469                elif data_type == 2:
470                    page.fill_data_combobox(data_2d_list)
471            else:
472                #the batch analysis is canceled
473                dlg.Destroy()
474                return None
475        else:
476            if page is None:
477                page = self.add_empty_page()
478            if data_1d_list and not data_2d_list:
479                #only on type of data
480                page.fill_data_combobox(data_1d_list)
481            elif not data_1d_list and data_2d_list:
482                page.fill_data_combobox(data_2d_list)
483
484        pos = self.GetPageIndex(page)
485        page.batch_on = self.batch_on
486        page._set_save_flag(not page.batch_on)
487        self.SetSelection(pos)
488        self.opened_pages[page.uid] = page
489        return page
490
491    def set_data(self, data_list):
492        """
493        Add a fitting page on the notebook contained by fitpanel
494
495        :param data: data to fit
496
497        :return panel : page just added for further used.
498        is used by fitting module
499
500        """
501        if not data_list:
502            return None
503        if self.batch_on:
504            return self.set_data_on_batch_mode(data_list)
505        else:
506            data = None
507            try:
508                data = data_list[0]
509            except:
510                # for 'fitv' files
511                data_list = [data]
512                data = data_list[0]
513
514            if data is None:
515                return None
516        for page in self.opened_pages.values():
517            #check if the selected data existing in the fitpanel
518            pos = self.GetPageIndex(page)
519            if not check_data_validity(page.get_data()) and not page.batch_on:
520                #make sure data get placed in 1D empty tab if data is 1D
521                #else data get place on 2D tab empty tab
522                enable2D = page.get_view_mode()
523                if (data.__class__.__name__ == "Data2D" and enable2D)\
524                or (data.__class__.__name__ == "Data1D" and not enable2D):
525                    page.batch_on = self.batch_on
526                    page._set_save_flag(not page.batch_on)
527                    page.fill_data_combobox(data_list)
528                    #caption = "FitPage" + str(self.fit_page_index)
529                    self.SetPageText(pos, page.window_caption)
530                    self.SetSelection(pos)
531                    return page
532        #create new page and add data
533        page = self.add_empty_page()
534        pos = self.GetPageIndex(page)
535        page.fill_data_combobox(data_list)
536        self.opened_pages[page.uid] = page
537        self.SetSelection(pos)
538        return page
539
540    def _onGetstate(self, event):
541        """
542        copy the state of a page
543        """
544        page = event.page
545        if page.uid in self.fit_page_name:
546            self.fit_page_name[page.uid].appendItem(page.createMemento())
547
548    def _onUndo(self, event):
549        """
550        return the previous state of a given page is available
551        """
552        page = event.page
553        if page.uid in self.fit_page_name:
554            if self.fit_page_name[page.uid].getCurrentPosition() == 0:
555                state = None
556            else:
557                state = self.fit_page_name[page.uid].getPreviousItem()
558                page._redo.Enable(True)
559            page.reset_page(state)
560
561    def _onRedo(self, event):
562        """
563        return the next state available
564        """
565        page = event.page
566        if page.uid in self.fit_page_name:
567            length = len(self.fit_page_name[page.uid])
568            if self.fit_page_name[page.uid].getCurrentPosition() == length - 1:
569                state = None
570                page._redo.Enable(False)
571                page._redo.Enable(True)
572            else:
573                state = self.fit_page_name[page.uid].getNextItem()
574            page.reset_page(state)
575
576    def _close_helper(self, selected_page):
577        """
578        Delete the given page from the notebook
579        """
580        #remove hint page
581        #if selected_page == self.hint_page:
582        #    return
583        ## removing sim_page
584        if selected_page == self.sim_page:
585            self._manager.sim_page = None
586            return
587        if selected_page == self.batch_page:
588            self._manager.batch_page = None
589            return
590        """
591        # The below is not working when delete #5 and still have #6.
592        if selected_page.__class__.__name__ == "FitPage":
593            self.fit_page_index -= 1
594        else:
595            self.batch_page_index -= 1
596        """
597        ## closing other pages
598        state = selected_page.createMemento()
599        page_finder = self._manager.get_page_finder()
600        ## removing fit page
601        data = selected_page.get_data()
602        #Don' t remove plot for 2D
603        flag = True
604        if data.__class__.__name__ == 'Data2D':
605            flag = False
606        if selected_page in page_finder:
607            #Delete the name of the page into the list of open page
608            for uid, list in self.opened_pages.iteritems():
609                #Don't return any panel is the exact same page is created
610
611                if flag and selected_page.uid == uid:
612                    self._manager.remove_plot(uid, theory=False)
613                    break
614            del page_finder[selected_page]
615
616        #Delete the name of the page into the list of open page
617        for uid, list in self.opened_pages.iteritems():
618            #Don't return any panel is the exact same page is created
619            if selected_page.uid == uid:
620                del self.opened_pages[selected_page.uid]
621                break
622        ##remove the check box link to the model name of this page (selected_page)
623        try:
624            self.sim_page.draw_page()
625        except:
626            ## that page is already deleted no need to remove check box on
627            ##non existing page
628            pass
629        try:
630            self.batch_page.draw_page()
631        except:
632            ## that page is already deleted no need to remove check box on
633            ##non existing page
634            pass
Note: See TracBrowser for help on using the repository browser.