source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @ 676f137

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 676f137 was 676f137, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Initial version of the C&S widget

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