source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @ 085409e3

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 085409e3 was 085409e3, checked in by Laura Forster <Awork@…>, 6 years ago

Fixed Copy and Paste bugs

There were several problems caused by the FittingWidget? being able to access all the open FitPage? tabs, meaning when Copy or Paste was used it was accessing multiple sets of params and models.

The functions are now being passed the currently open tab, not all open tabs.

  • 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.setFittingStarted()
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.setFittingStopped()
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.