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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 67b0a99 was 67b0a99, checked in by krzywon, 7 years ago

#761: Constrained fit pages are now deleted if they no longer have any data in them when other data sets are deleted. #826: Fixed a bug where dQ smearing option was unavailable for 1D data sets.

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