source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @ 7fd20fc

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 7fd20fc was e90988c, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Show help pages in default browser. Fixed some help links and modified unit tests. SASVIEW-800

  • Property mode set to 100644
File size: 7.7 KB
Line 
1import numpy
2
3from PyQt5 import QtCore
4from PyQt5 import QtGui
5from PyQt5 import QtWidgets
6
7from bumps import options
8from bumps import fitters
9
10import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary
11
12from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget
13from sas.qtgui.Perspectives.Fitting.FittingOptions import FittingOptions
14from sas.qtgui.Perspectives.Fitting.GPUOptions import GPUOptions
15
16class FittingWindow(QtWidgets.QTabWidget):
17    """
18    """
19    name = "Fitting" # For displaying in the combo box in DataExplorer
20    def __init__(self, parent=None, data=None):
21
22        super(FittingWindow, self).__init__()
23
24        self.parent = parent
25        self._data = data
26
27        # List of active fits
28        self.tabs = []
29
30        # Max index for adding new, non-clashing tab names
31        self.maxIndex = 0
32
33        # Index of the current tab
34        self.currentTab = 0
35
36        # The default optimizer
37        self.optimizer = 'Levenberg-Marquardt'
38
39        # Dataset inde -> Fitting tab mapping
40        self.dataToFitTab = {}
41
42        # The tabs need to be closeable
43        self.setTabsClosable(True)
44
45        self.communicate = self.parent.communicator()
46
47        # Initialize the first tab
48        self.addFit(None)
49
50        # Deal with signals
51        self.tabCloseRequested.connect(self.tabCloses)
52        self.communicate.dataDeletedSignal.connect(self.dataDeleted)
53
54        # Perspective window not allowed to close by default
55        self._allow_close = False
56
57        # Fit options - uniform for all tabs
58        self.fit_options = options.FIT_CONFIG
59        self.fit_options_widget = FittingOptions(self, config=self.fit_options)
60        self.fit_options.selected_id = fitters.LevenbergMarquardtFit.id
61
62        # Listen to GUI Manager signal updating fit options
63        self.fit_options_widget.fit_option_changed.connect(self.onFittingOptionsChange)
64
65        # GPU Options
66        self.gpu_options_widget = GPUOptions(self)
67
68        #self.setWindowTitle('Fit panel - Active Fitting Optimizer: %s' % self.optimizer)
69        self.updateWindowTitle()
70
71    def updateWindowTitle(self):
72        """
73        Update the window title with the current optimizer name
74        """
75        self.optimizer = self.fit_options.selected_name
76        self.setWindowTitle('Fit panel - Active Fitting Optimizer: %s' % self.optimizer)
77
78
79    def setClosable(self, value=True):
80        """
81        Allow outsiders close this widget
82        """
83        assert isinstance(value, bool)
84
85        self._allow_close = value
86
87    def closeEvent(self, event):
88        """
89        Overwrite QDialog close method to allow for custom widget close
90        """
91        # Invoke fit page events
92        if self._allow_close:
93            # reset the closability flag
94            self.setClosable(value=False)
95            # Tell the MdiArea to close the container
96            self.parentWidget().close()
97            event.accept()
98        else:
99            # Maybe we should just minimize
100            self.setWindowState(QtCore.Qt.WindowMinimized)
101            event.ignore()
102
103    def addFit(self, data, is_batch=False):
104        """
105        Add a new tab for passed data
106        """
107        tab     = FittingWidget(parent=self.parent, data=data, tab_id=self.maxIndex+1)
108        tab.is_batch_fitting = is_batch
109        # Add this tab to the object library so it can be retrieved by scripting/jupyter
110        tab_name = self.tabName(is_batch=is_batch)
111        ObjectLibrary.addObject(tab_name, tab)
112        self.tabs.append(tab)
113        if data:
114            self.updateFitDict(data, tab_name)
115        self.maxIndex += 1
116        self.addTab(tab, tab_name)
117
118    def updateFitDict(self, item_key, tab_name):
119        """
120        Create a list if none exists and append if there's already a list
121        """
122        item_key_str = str(item_key)
123        if item_key_str in list(self.dataToFitTab.keys()):
124            self.dataToFitTab[item_key_str].append(tab_name)
125        else:
126            self.dataToFitTab[item_key_str] = [tab_name]
127
128        #print "CURRENT dict: ", self.dataToFitTab
129
130    def tabName(self, is_batch=False):
131        """
132        Get the new tab name, based on the number of fitting tabs so far
133        """
134        page_name = "BatchPage" if is_batch else "FitPage"
135        page_name = page_name + str(self.maxIndex)
136        return page_name
137
138    def resetTab(self, index):
139        """
140        Adds a new tab and removes the last tab
141        as a way of resetting the fit tabs
142        """
143        # If data on tab empty - do nothing
144        if index in self.tabs and not self.tabs[index].data:
145            return
146        # Add a new, empy tab
147        self.addFit(None)
148        # Remove the previous last tab
149        self.tabCloses(index)
150
151    def tabCloses(self, index):
152        """
153        Update local bookkeeping on tab close
154        """
155        #assert len(self.tabs) >= index
156        # don't remove the last tab
157        if len(self.tabs) <= 1:
158            self.resetTab(index)
159            return
160        try:
161            ObjectLibrary.deleteObjectByRef(self.tabs[index])
162            self.removeTab(index)
163            del self.tabs[index]
164        except IndexError:
165            # The tab might have already been deleted previously
166            pass
167
168    def closeTabByName(self, tab_name):
169        """
170        Given name of the fitting tab - close it
171        """
172        for tab_index in range(len(self.tabs)):
173            if self.tabText(tab_index) == tab_name:
174                self.tabCloses(tab_index)
175        pass # debug hook
176
177    def dataDeleted(self, index_list):
178        """
179        Delete fit tabs referencing given data
180        """
181        if not index_list or not self.dataToFitTab:
182            return
183        for index_to_delete in index_list:
184            index_to_delete_str = str(index_to_delete)
185            if index_to_delete_str in list(self.dataToFitTab.keys()):
186                for tab_name in self.dataToFitTab[index_to_delete_str]:
187                    # delete tab #index after corresponding data got removed
188                    self.closeTabByName(tab_name)
189                self.dataToFitTab.pop(index_to_delete_str)
190
191        #print "CURRENT dict: ", self.dataToFitTab
192
193    def allowBatch(self):
194        """
195        Tell the caller that we accept multiple data instances
196        """
197        return True
198
199    def setData(self, data_item=None, is_batch=False):
200        """
201        Assign new dataset to the fitting instance
202        Obtain a QStandardItem object and dissect it to get Data1D/2D
203        Pass it over to the calculator
204        """
205        assert data_item is not None
206
207        if not isinstance(data_item, list):
208            msg = "Incorrect type passed to the Fitting Perspective"
209            raise AttributeError(msg)
210
211        if not isinstance(data_item[0], QtGui.QStandardItem):
212            msg = "Incorrect type passed to the Fitting Perspective"
213            raise AttributeError(msg)
214
215        items = [data_item] if is_batch else data_item
216
217        for data in items:
218            # Find the first unassigned tab.
219            # If none, open a new tab.
220            available_tabs = list([tab.acceptsData() for tab in self.tabs])
221
222            if numpy.any(available_tabs):
223                first_good_tab = available_tabs.index(True)
224                self.tabs[first_good_tab].data = data
225                tab_name = str(self.tabText(first_good_tab))
226                self.updateFitDict(data, tab_name)
227            else:
228                self.addFit(data, is_batch=is_batch)
229
230    def onFittingOptionsChange(self, fit_engine):
231        """
232        React to the fitting algorithm change by modifying window title
233        """
234        fitter = [f.id for f in options.FITTERS if f.name == str(fit_engine)][0]
235        # set the optimizer
236        self.fit_options.selected_id = str(fitter)
237        # Update the title
238        self.updateWindowTitle()
239
240        pass
Note: See TracBrowser for help on using the repository browser.