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

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

Only save fit pages with a data set and model selected. see #960

  • Property mode set to 100644
File size: 22.3 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, StatusEvent
13from sas.sasgui.guiframe.dataFitting import check_data_validity
14from sas.sasgui.perspectives.fitting.simfitpage import SimultaneousFitPage
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        # Iterate through all pages and check for batch fitting
86        batch_state = None
87        if self.sim_page is not None:
88            batch_state = self.sim_page.set_state()
89
90        for uid, page in self.opened_pages.iteritems():
91            data = page.get_data()
92            # state must be cloned
93            state = page.get_state().clone()
94            # data_list only populated with real data
95            # Fake object in data from page.get_data() if model is selected
96            if len(page.data_list) is not 0 and page.model is not None:
97                new_doc = self._manager.state_reader.write_toXML(data,
98                                                                 state,
99                                                                 batch_state)
100                # Fit #2 through #n are append to first fit
101                if doc is not None and hasattr(doc, "firstChild"):
102                    # Only append if properly formed new_doc
103                    if new_doc is not None and hasattr(new_doc, "firstChild"):
104                        child = new_doc.firstChild.firstChild
105                        doc.firstChild.appendChild(child)
106                # First fit defines the main document
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        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,
322                                                 id=wx.ID_ANY, batch_on=False)
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
340
341    def add_empty_page(self):
342        """
343        add an empty page
344        """
345        if self.batch_on:
346            from batchfitpage import BatchFitPage
347            panel = BatchFitPage(parent=self)
348            self.batch_page_index += 1
349            caption = "BatchPage" + str(self.batch_page_index)
350            panel.set_index_model(self.batch_page_index)
351        else:
352            # Increment index of fit page
353            from fitpage import FitPage
354            panel = FitPage(parent=self)
355            self.fit_page_index += 1
356            caption = "FitPage" + str(self.fit_page_index)
357            panel.set_index_model(self.fit_page_index)
358        panel.batch_on = self.batch_on
359        panel._set_save_flag(not panel.batch_on)
360        panel.set_model_dictionary(self.model_dictionary)
361        panel.populate_box(model_dict=self.model_list_box)
362        panel.formfactor_combo_init()
363        panel.set_manager(self._manager)
364        panel.window_caption = caption
365        panel.window_name = caption
366        self.AddPage(panel, caption, select=True)
367        self.opened_pages[panel.uid] = panel
368        self._manager.create_fit_problem(panel.uid)
369        self._manager.page_finder[panel.uid].add_data(panel.get_data())
370        self.enable_close_button()
371        panel.on_set_focus(None)
372        return panel
373
374    def enable_close_button(self):
375        """
376        display the close button on tab for more than 1 tabs else remove the
377        close button
378        """
379        if self.GetPageCount() <= 1:
380            style = self.GetWindowStyleFlag()
381            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
382            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB == flag:
383                style = style & ~wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
384                self.SetWindowStyle(style)
385        else:
386            style = self.GetWindowStyleFlag()
387            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
388            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB != flag:
389                style |= wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
390                self.SetWindowStyle(style)
391
392    def delete_data(self, data):
393        """
394        Delete the given data
395        """
396        if data.__class__.__name__ != "list":
397            raise ValueError, "Fitpanel delete_data expect list of id"
398        else:
399            for page in self.opened_pages.values():
400                pos = self.GetPageIndex(page)
401                temp_data = page.get_data()
402                if temp_data is not None and temp_data.id in data:
403                    self.close_page_with_data(temp_data)
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(pos)
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            from sas.sasgui.perspectives.fitting.fitting_widgets import \
449                BatchDataDialog
450            dlg = BatchDataDialog(self)
451            if dlg.ShowModal() == wx.ID_OK:
452                data_type = dlg.get_data()
453                dlg.Destroy()
454                if page is None:
455                    page = self.add_empty_page()
456                if data_type == 1:
457                    # user has selected only data1D
458                    page.fill_data_combobox(data_1d_list)
459                elif data_type == 2:
460                    page.fill_data_combobox(data_2d_list)
461            else:
462                # the batch analysis is canceled
463                dlg.Destroy()
464                return None
465        else:
466            if page is None:
467                page = self.add_empty_page()
468            if data_1d_list and not data_2d_list:
469                # only on type of data
470                page.fill_data_combobox(data_1d_list)
471            elif not data_1d_list and data_2d_list:
472                page.fill_data_combobox(data_2d_list)
473
474        pos = self.GetPageIndex(page)
475        page.batch_on = self.batch_on
476        page._set_save_flag(not page.batch_on)
477        self.SetSelection(pos)
478        self.opened_pages[page.uid] = page
479        return page
480
481    def set_data(self, data_list):
482        """
483        Add a fitting page on the notebook contained by fitpanel
484
485        :param data_list: data to fit
486
487        :return panel : page just added for further used.
488        is used by fitting module
489
490        """
491        if not data_list:
492            return None
493        if self.batch_on:
494            return self.set_data_on_batch_mode(data_list)
495        else:
496            data = None
497            try:
498                data = data_list[0]
499            except Exception:
500                # for 'fitv' files
501                data_list = [data]
502                data = data_list[0]
503
504            if data is None:
505                return None
506        for page in self.opened_pages.values():
507            # check if the selected data existing in the fitpanel
508            pos = self.GetPageIndex(page)
509            if not check_data_validity(page.get_data()) and not page.batch_on:
510                # make sure data get placed in 1D empty tab if data is 1D
511                # else data get place on 2D tab empty tab
512                enable2D = page.get_view_mode()
513                if (data.__class__.__name__ == "Data2D" and enable2D)\
514                   or (data.__class__.__name__ == "Data1D" and not enable2D):
515                    page.batch_on = self.batch_on
516                    page._set_save_flag(not page.batch_on)
517                    page.fill_data_combobox(data_list)
518                    # caption = "FitPage" + str(self.fit_page_index)
519                    self.SetPageText(pos, page.window_caption)
520                    self.SetSelection(pos)
521                    return page
522        # create new page and add data
523        page = self.add_empty_page()
524        pos = self.GetPageIndex(page)
525        page.fill_data_combobox(data_list)
526        self.opened_pages[page.uid] = page
527        self.SetSelection(pos)
528        return page
529
530    def _onGetstate(self, event):
531        """
532        copy the state of a page
533        """
534        page = event.page
535        if page.uid in self.fit_page_name:
536            self.fit_page_name[page.uid].appendItem(page.createMemento())
537
538    def _onUndo(self, event):
539        """
540        return the previous state of a given page is available
541        """
542        page = event.page
543        if page.uid in self.fit_page_name:
544            if self.fit_page_name[page.uid].getCurrentPosition() == 0:
545                state = None
546            else:
547                state = self.fit_page_name[page.uid].getPreviousItem()
548                page._redo.Enable(True)
549            page.reset_page(state)
550
551    def _onRedo(self, event):
552        """
553        return the next state available
554        """
555        page = event.page
556        if page.uid in self.fit_page_name:
557            length = len(self.fit_page_name[page.uid])
558            if self.fit_page_name[page.uid].getCurrentPosition() == length - 1:
559                state = None
560                page._redo.Enable(False)
561                page._redo.Enable(True)
562            else:
563                state = self.fit_page_name[page.uid].getNextItem()
564            page.reset_page(state)
565
566    def _close_helper(self, selected_page):
567        """
568        Delete the given page from the notebook
569        """
570        # remove hint page
571        # if selected_page == self.hint_page:
572        #    return
573        # removing sim_page
574        if selected_page == self.sim_page:
575            self._manager.sim_page = None
576            return
577        if selected_page == self.batch_page:
578            self._manager.batch_page = None
579            return
580        # closing other pages
581        state = selected_page.createMemento()
582        page_finder = self._manager.get_page_finder()
583        # removing fit page
584        data = selected_page.get_data()
585        # Don' t remove plot for 2D
586        flag = True
587        if data.__class__.__name__ == 'Data2D':
588            flag = False
589        if selected_page in page_finder:
590            # Delete the name of the page into the list of open page
591            for uid, list in self.opened_pages.iteritems():
592                # Don't return any panel is the exact same page is created
593                if flag and selected_page.uid == uid:
594                    self._manager.remove_plot(uid, theory=False)
595                    break
596            del page_finder[selected_page]
597
598        # Delete the name of the page into the list of open page
599        for uid, list in self.opened_pages.iteritems():
600            # Don't return any panel is the exact same page is created
601            if selected_page.uid == uid:
602                del self.opened_pages[selected_page.uid]
603                break
604        # remove the check box link to the model name of the selected_page
605        try:
606            self.sim_page.draw_page()
607        except:
608            # that page is already deleted no need to remove check box on
609            # non existing page
610            pass
611        try:
612            self.batch_page.draw_page()
613        except:
614            # that page is already deleted no need to remove check box on
615            # non existing page
616            pass
Note: See TracBrowser for help on using the repository browser.