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

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 dd150ef was 4992ff2, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Initial, in-progress version. Not really working atm. SASVIEW-787

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