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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 1386b2f was 277257f, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

clean up plugin-model handling code; preserve active parameter values when plugin is updated

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