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

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since a95ae9a was a95ae9a, checked in by krzywon, 8 years ago

#189: Modified save project to include information from simultaneous fit panel. Next step is to load in the parameters and repopulate the simultaneous and constrained fit panel.

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