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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249unittest-saveload
Last change on this file since 7e736c6 was 69363c7, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

Merge branch 'master' into ticket-853-fit-gui-to-calc

  • Property mode set to 100644
File size: 22.4 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            # data_list only populated with real data
101            # Fake object in data from page.get_data() if model is selected
102            if len(page.data_list) is not 0 and page.model is not None:
103                new_doc = self._manager.state_reader.write_toXML(data,
104                                                                 state,
105                                                                 batch_state)
106                # Fit #2 through #n are append to first fit
107                if doc is not None and hasattr(doc, "firstChild"):
108                    # Only append if properly formed new_doc
109                    if new_doc is not None and hasattr(new_doc, "firstChild"):
110                        child = new_doc.firstChild.firstChild
111                        doc.firstChild.appendChild(child)
112                # First fit defines the main document
113                else:
114                    doc = new_doc
115
116        return doc
117
118    def update_model_list(self):
119        """
120        """
121        temp = self.menu_mng.update()
122        if temp:
123            self.model_list_box = temp
124        return temp
125
126    def reset_pmodel_list(self):
127        """
128        """
129        self.model_list_box = self.menu_mng.plugins_reset()
130        return self.model_list_box
131
132    def get_page_by_id(self, uid):
133        """
134        """
135        if uid not in self.opened_pages:
136            msg = "Fitpanel cannot find ID: %s in self.opened_pages" % str(uid)
137            raise ValueError, msg
138        else:
139            return self.opened_pages[uid]
140
141    def on_page_changing(self, event):
142        """
143        calls the function when the current event handler has exited. avoiding
144        to call panel on focus on a panel that is currently deleted
145        """
146        wx.CallAfter(self.helper_on_page_change)
147
148    def helper_on_page_change(self):
149        """
150        """
151        pos = self.GetSelection()
152        if pos != -1:
153            selected_page = self.GetPage(pos)
154            wx.PostEvent(self._manager.parent,
155                         PanelOnFocusEvent(panel=selected_page))
156        self.enable_close_button()
157
158    def on_set_focus(self, event):
159        """
160        """
161        pos = self.GetSelection()
162        if pos != -1:
163            selected_page = self.GetPage(pos)
164            wx.PostEvent(self._manager.parent,
165                         PanelOnFocusEvent(panel=selected_page))
166
167    def get_data(self):
168        """
169        get the data in the current page
170        """
171        pos = self.GetSelection()
172        if pos != -1:
173            selected_page = self.GetPage(pos)
174            return selected_page.get_data()
175
176    def set_model_state(self, state):
177        """
178        receive a state to reset the model in the current page
179        """
180        pos = self.GetSelection()
181        if pos != -1:
182            selected_page = self.GetPage(pos)
183            selected_page.set_model_state(state)
184
185    def get_state(self):
186        """
187        return the state of the current selected page
188        """
189        pos = self.GetSelection()
190        if pos != -1:
191            selected_page = self.GetPage(pos)
192            return selected_page.get_state()
193
194    def close_all(self):
195        """
196        remove all pages, used when a svs file is opened
197        """
198
199        # use while-loop, for-loop will not do the job well.
200        while (self.GetPageCount() > 0):
201            page = self.GetPage(self.GetPageCount() - 1)
202            if self._manager.parent.panel_on_focus == page:
203                self._manager.parent.panel_on_focus = None
204            self._close_helper(selected_page=page)
205            self.DeletePage(self.GetPageCount() - 1)
206        # Clear list of names
207        self.fit_page_name = {}
208        # Clear list of opened pages
209        self.opened_pages = {}
210        self.fit_page_index = 0
211        self.batch_page_index = 0
212
213    def set_state(self, state):
214        """
215        Restore state of the panel
216        """
217        page_is_opened = False
218        if state is not None:
219            for uid, panel in self.opened_pages.iteritems():
220                # Don't return any panel is the exact same page is created
221                if uid == panel.uid and panel.data == state.data:
222                    # the page is still opened
223                    panel.reset_page(state=state)
224                    panel.save_current_state()
225                    page_is_opened = True
226            if not page_is_opened:
227                if state.data.__class__.__name__ != 'list':
228                    # To support older state file format
229                    list_data = [state.data]
230                else:
231                    # Todo: need new file format for the list
232                    list_data = state.data
233                panel = self._manager.add_fit_page(data=list_data)
234                # add data associated to the page created
235                if panel is not None:
236                    self._manager.store_data(uid=panel.uid,
237                                             data_list=list_data,
238                                             caption=panel.window_caption)
239                    panel.reset_page(state=state)
240                    panel.save_current_state()
241
242    def clear_panel(self):
243        """
244        Clear and close all panels, used by guimanager
245        """
246        # close all panels only when svs file opened
247        self.close_all()
248        self.sim_page = None
249        self.batch_page = None
250
251    def on_close_page(self, event=None):
252        """
253        close page and remove all references to the closed page
254        """
255        selected_page = self.GetPage(self.GetSelection())
256        if self.GetPageCount() == 1:
257            if selected_page.get_data() is not None:
258                if event is not None:
259                    event.Veto()
260                return
261        self._close_helper(selected_page=selected_page)
262
263    def close_page_with_data(self, deleted_data):
264        """
265        close a fit page when its data is completely remove from the graph
266        """
267        if deleted_data is None:
268            return
269        for index in range(self.GetPageCount()):
270            selected_page = self.GetPage(index)
271            if hasattr(selected_page, "get_data"):
272                data = selected_page.get_data()
273
274                if data is None:
275                    # the fitpanel exists and only the initial fit page is open
276                    # with no selected data
277                    return
278                if data.id == deleted_data.id:
279                    self._close_helper(selected_page)
280                    self.DeletePage(index)
281                    break
282
283    def set_manager(self, manager):
284        """
285        set panel manager
286
287        :param manager: instance of plugin fitting
288        """
289        self._manager = manager
290        for pos in range(self.GetPageCount()):
291            page = self.GetPage(pos)
292            if page is not None:
293                page.set_manager(self._manager)
294
295    def set_model_list(self, dict):
296        """
297        copy a dictionary of model into its own dictionary
298
299        :param dict: dictionnary made of model name as key and model class
300            as value
301        """
302        self.model_list_box = dict
303
304    def set_model_dictionary(self, model_dictionary):
305        """
306        copy a dictionary of model name -> model object
307
308        :param model_dictionary: dictionary linking model name -> model object
309        """
310
311    def get_current_page(self):
312        """
313        :return: the current page selected
314
315        """
316        return self.GetPage(self.GetSelection())
317
318    def add_sim_page(self, caption="Const & Simul Fit"):
319        """
320        Add the simultaneous fit page
321        """
322        page_finder = self._manager.get_page_finder()
323        if caption == "Const & Simul Fit":
324            self.sim_page = SimultaneousFitPage(self, page_finder=page_finder,
325                                                 id=wx.ID_ANY, batch_on=False)
326            self.sim_page.window_caption = caption
327            self.sim_page.window_name = caption
328            self.sim_page.uid = wx.NewId()
329            self.AddPage(self.sim_page, caption, True)
330            self.sim_page.set_manager(self._manager)
331            self.enable_close_button()
332            return self.sim_page
333        else:
334            self.batch_page = SimultaneousFitPage(self, batch_on=True,
335                                                  page_finder=page_finder)
336            self.batch_page.window_caption = caption
337            self.batch_page.window_name = caption
338            self.batch_page.uid = wx.NewId()
339            self.AddPage(self.batch_page, caption, True)
340            self.batch_page.set_manager(self._manager)
341            self.enable_close_button()
342            return self.batch_page
343
344    def add_empty_page(self):
345        """
346        add an empty page
347        """
348        if self.batch_on:
349            panel = BatchFitPage(parent=self)
350            self.batch_page_index += 1
351            caption = "BatchPage" + str(self.batch_page_index)
352            panel.set_index_model(self.batch_page_index)
353        else:
354            # Increment index of fit page
355            panel = FitPage(parent=self)
356            self.fit_page_index += 1
357            caption = "FitPage" + str(self.fit_page_index)
358            panel.set_index_model(self.fit_page_index)
359        panel.batch_on = self.batch_on
360        panel._set_save_flag(not panel.batch_on)
361        panel.set_model_dictionary(self.model_dictionary)
362        panel.populate_box(model_list_box=self.model_list_box)
363        panel.formfactor_combo_init()
364        panel.set_manager(self._manager)
365        panel.window_caption = caption
366        panel.window_name = caption
367        self.AddPage(panel, caption, select=True)
368        self.opened_pages[panel.uid] = panel
369        self._manager.create_fit_problem(panel.uid)
370        self._manager.page_finder[panel.uid].add_data(panel.get_data())
371        self.enable_close_button()
372        panel.on_set_focus(None)
373        return panel
374
375    def enable_close_button(self):
376        """
377        display the close button on tab for more than 1 tabs else remove the
378        close button
379        """
380        if self.GetPageCount() <= 1:
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 = style & ~wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
385                self.SetWindowStyle(style)
386        else:
387            style = self.GetWindowStyleFlag()
388            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
389            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB != flag:
390                style |= wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
391                self.SetWindowStyle(style)
392
393    def delete_data(self, data):
394        """
395        Delete the given data
396        """
397        if data.__class__.__name__ != "list":
398            raise ValueError, "Fitpanel delete_data expect list of id"
399        else:
400            for page in self.opened_pages.values():
401                pos = self.GetPageIndex(page)
402                temp_data = page.get_data()
403                if temp_data is not None and temp_data.id in data:
404                    self.close_page_with_data(temp_data)
405            if self.sim_page is not None:
406                if len(self.sim_page.model_list) == 0:
407                    pos = self.GetPageIndex(self.sim_page)
408                    self.SetSelection(pos)
409                    self.on_close_page(event=None)
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            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        focused_page = self.GetPage(self.GetSelection())
505        for page in self.opened_pages.values():
506            # check if the selected data existing in the fitpanel
507            pos = self.GetPageIndex(page)
508            if not check_data_validity(page.get_data()) and not page.batch_on:
509                if page.model is not None and page != focused_page:
510                    # Page has an active theory and is in background - don't
511                    # send data here.
512                    continue
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
525        # create new page and add data
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
532
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())
540
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)
553
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)
568
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        # closing other pages
584        state = selected_page.createMemento()
585        page_finder = self._manager.get_page_finder()
586        # removing fit page
587        data = selected_page.get_data()
588        # Don' t remove plot for 2D
589        flag = True
590        if data.__class__.__name__ == 'Data2D':
591            flag = False
592        if selected_page in page_finder:
593            # Delete the name of the page into the list of open page
594            for uid, list in self.opened_pages.iteritems():
595                # Don't return any panel is the exact same page is created
596                if flag and selected_page.uid == uid:
597                    self._manager.remove_plot(uid, theory=False)
598                    break
599            del page_finder[selected_page]
600
601        # Delete the name of the page into the list of open page
602        for uid, list in self.opened_pages.iteritems():
603            # Don't return any panel is the exact same page is created
604            if selected_page.uid == uid:
605                del self.opened_pages[selected_page.uid]
606                break
607        # remove the check box link to the model name of the selected_page
608        try:
609            self.sim_page.draw_page()
610        except:
611            # that page is already deleted no need to remove check box on
612            # non existing page
613            pass
614        try:
615            self.batch_page.draw_page()
616        except:
617            # that page is already deleted no need to remove check box on
618            # non existing page
619            pass
Note: See TracBrowser for help on using the repository browser.