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

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 d744767 was 14ec91c5, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

More code review related fixes

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