source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @ 186d678

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 186d678 was a3c59503, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Working project load/save. new format only. SASVIEW-983/984

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