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

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.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since fa09d62 was fa09d62, checked in by krzywon, 9 years ago

Modified the batch fit panel to give a warning/message saying the file
selecctor is for selecting initial parameters rather than viewing fits.
This is a fix to ticket #234.

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