source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @ 63319b0

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

unit tests for constraints: FittingWidget?, FittingPerspective?

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