source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @ 9b9ec10

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 9b9ec10 was 9b9ec10, checked in by Laura Forster <Awork@…>, 3 years ago

Merge branch 'ESS_GUI' of https://github.com/SasView/sasview into ESS_GUI

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