source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @ 42d79fc

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 42d79fc was 42d79fc, checked in by wojciech, 2 years ago

Disabling reporting for perspectives other than fitting

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