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

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 65f3930 was 65f3930, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

move models.py to sascalc/fit

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