source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingOptions.py @ 8ef789c

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_opencl
Last change on this file since 8ef789c was 33c0561, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Replace Apply button menu driven functionality with additional button.
Removed Cancel.
Removed the window system context help button from all affected widgets.
SASVIEW-1239

  • Property mode set to 100644
File size: 10.0 KB
RevLine 
[2d0e0c1]1# global
2import sys
3import os
[b0c5e8c]4import types
[e90988c]5import webbrowser
[b0c5e8c]6
[4992ff2]7from PyQt5 import QtCore
8from PyQt5 import QtGui
9from PyQt5 import QtWidgets
[2d0e0c1]10
11from sas.qtgui.UI import images_rc
12from sas.qtgui.UI import main_resources_rc
[b0c5e8c]13import sas.qtgui.Utilities.GuiUtils as GuiUtils
14
[2d0e0c1]15from bumps import fitters
16import bumps.options
17
18from sas.qtgui.Perspectives.Fitting.UI.FittingOptionsUI import Ui_FittingOptions
19
20# Set the default optimizer
21fitters.FIT_DEFAULT_ID = 'lm'
22
[72f4834]23
[4992ff2]24class FittingOptions(QtWidgets.QDialog, Ui_FittingOptions):
[2d0e0c1]25    """
26    Hard-coded version of the fit options dialog available from BUMPS.
27    This should be make more "dynamic".
28    bumps.options.FIT_FIELDS gives mapping between parameter names, parameter strings and field type
29     (double line edit, integer line edit, combo box etc.), e.g.
30        FIT_FIELDS = dict(
31            samples = ("Samples", parse_int),
32            xtol = ("x tolerance", float))
33
34    bumps.fitters.<algorithm>.settings gives mapping between algorithm, parameter name and default value:
35        e.g.
36        settings = [('steps', 1000), ('starts', 1), ('radius', 0.15), ('xtol', 1e-6), ('ftol', 1e-8)]
37    """
[b0c5e8c]38    fit_option_changed = QtCore.pyqtSignal(str)
[2d0e0c1]39
40    def __init__(self, parent=None, config=None):
41        super(FittingOptions, self).__init__(parent)
42        self.setupUi(self)
[33c0561]43        # disable the context help icon
44        self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
[2d0e0c1]45
46        self.config = config
47
48        # no reason to have this widget resizable
49        self.setFixedSize(self.minimumSizeHint())
50
[85487ebd]51        self.setWindowTitle("Fit Algorithms")
[2d0e0c1]52
53        # Fill up the algorithm combo, based on what BUMPS says is available
54        self.cbAlgorithm.addItems([n.name for n in fitters.FITTERS if n.id in fitters.FIT_ACTIVE_IDS])
55
56        # Handle the Apply button click
[4992ff2]57        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onApply)
[b0c5e8c]58        # handle the Help button click
[4992ff2]59        self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self.onHelp)
[2d0e0c1]60
61        # Handle the combo box changes
62        self.cbAlgorithm.currentIndexChanged.connect(self.onAlgorithmChange)
63
64        # Set the default index
65        default_name = [n.name for n in fitters.FITTERS if n.id == fitters.FIT_DEFAULT_ID][0]
66        default_index = self.cbAlgorithm.findText(default_name)
67        self.cbAlgorithm.setCurrentIndex(default_index)
[8873ab7]68        # previous algorithm choice
69        self.previous_index = default_index
[b0c5e8c]70
71        # Assign appropriate validators
72        self.assignValidators()
73
[72f4834]74        # Set defaults
[2d0e0c1]75        self.current_fitter_id = fitters.FIT_DEFAULT_ID
76
[72f4834]77        # OK has to be initialized to True, after initial validator setup
[4992ff2]78        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
[72f4834]79
[b0c5e8c]80    def assignValidators(self):
81        """
82        Use options.FIT_FIELDS to assert which line edit gets what validator
83        """
[b3e8629]84        for option in bumps.options.FIT_FIELDS.keys():
[b0c5e8c]85            (f_name, f_type) = bumps.options.FIT_FIELDS[option]
86            validator = None
87            if type(f_type) == types.FunctionType:
88                validator = QtGui.QIntValidator()
[72f4834]89                validator.setBottom(0)
[b3e8629]90            elif f_type == float:
[d6b8a1d]91                validator = GuiUtils.DoubleValidator()
[72f4834]92                validator.setBottom(0)
[b0c5e8c]93            else:
94                continue
95            for fitter_id in fitters.FIT_ACTIVE_IDS:
96                line_edit = self.widgetFromOption(str(option), current_fitter=str(fitter_id))
97                if hasattr(line_edit, 'setValidator') and validator is not None:
98                    line_edit.setValidator(validator)
[72f4834]99                    line_edit.textChanged.connect(self.check_state)
100                    line_edit.textChanged.emit(line_edit.text())
101
102    def check_state(self, *args, **kwargs):
103        sender = self.sender()
104        validator = sender.validator()
105        state = validator.validate(sender.text(), 0)[0]
106        if state == QtGui.QValidator.Acceptable:
107            color = '' # default
[4992ff2]108            self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
[72f4834]109        else:
110            color = '#fff79a' # yellow
[4992ff2]111            self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
[72f4834]112
113        sender.setStyleSheet('QLineEdit { background-color: %s }' % color)
[2d0e0c1]114
115    def onAlgorithmChange(self, index):
116        """
117        Change the page in response to combo box index
118        """
119        # Find the algorithm ID from name
120        self.current_fitter_id = \
121            [n.id for n in fitters.FITTERS if n.name == str(self.cbAlgorithm.currentText())][0]
122
123        # find the right stacked widget
124        widget_name = "self.page_"+str(self.current_fitter_id)
125
126        # Convert the name into widget instance
[8873ab7]127        try:
128            widget_to_activate = eval(widget_name)
129        except AttributeError:
130            # We don't yet have this optimizer.
131            # Show message
132            msg = "This algorithm has not yet been implemented in SasView.\n"
133            msg += "Please choose a different algorithm"
134            QtWidgets.QMessageBox.warning(self,
135                                        'Warning',
136                                        msg,
137                                        QtWidgets.QMessageBox.Ok)
138            # Move the index to previous position
139            self.cbAlgorithm.setCurrentIndex(self.previous_index)
140            return
141
[b0c5e8c]142        index_for_this_id = self.stackedWidget.indexOf(widget_to_activate)
[2d0e0c1]143
144        # Select the requested widget
[b0c5e8c]145        self.stackedWidget.setCurrentIndex(index_for_this_id)
[2d0e0c1]146
147        self.updateWidgetFromBumps(self.current_fitter_id)
148
[b0c5e8c]149        self.assignValidators()
150
[b67bfa7]151        # OK has to be reinitialized to True
[4992ff2]152        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
[b67bfa7]153
[8873ab7]154        # keep reference
155        self.previous_index = index
156
[2d0e0c1]157    def onApply(self):
158        """
[b0c5e8c]159        Update the fitter object
[2d0e0c1]160        """
[ff3b293]161        options = self.config.values[self.current_fitter_id]
162        for option in options.keys():
163            # Find the widget name of the option
164            # e.g. 'samples' for 'dream' is 'self.samples_dream'
165            widget_name = 'self.'+option+'_'+self.current_fitter_id
[8873ab7]166            try:
167                line_edit = eval(widget_name)
168            except AttributeError:
169                # Skip bumps monitors
170                continue
[ff3b293]171            if line_edit is None or not isinstance(line_edit, QtWidgets.QLineEdit):
172                continue
173            color = line_edit.palette().color(QtGui.QPalette.Background).name()
174            if color == '#fff79a':
175                # Show a custom tooltip and return
176                tooltip = "<html><b>Please enter valid values in all fields.</html>"
177                QtWidgets.QToolTip.showText(line_edit.mapToGlobal(
178                    QtCore.QPoint(line_edit.rect().right(), line_edit.rect().bottom() + 2)), tooltip)
179                return
180
[b0c5e8c]181        # Notify the perspective, so the window title is updated
182        self.fit_option_changed.emit(self.cbAlgorithm.currentText())
[2d0e0c1]183
[b0c5e8c]184        def bumpsUpdate(option):
185            """
186            Utility method for bumps state update
187            """
[2d0e0c1]188            widget = self.widgetFromOption(option)
[f7d39c9]189            if widget is None:
190                return
191            try:
[8873ab7]192                if isinstance(widget, QtWidgets.QComboBox):
193                    new_value = widget.currentText()
194                else:
195                    try:
196                        new_value = int(widget.text())
197                    except ValueError:
198                        new_value = float(widget.text())
199                #new_value = widget.currentText() if isinstance(widget, QtWidgets.QComboBox) \
200                #    else float(widget.text())
[f7d39c9]201                self.config.values[self.current_fitter_id][option] = new_value
202            except ValueError:
203                # Don't update bumps if widget has bad data
204                self.reject
[2d0e0c1]205
[b0c5e8c]206        # Update the BUMPS singleton
[b3e8629]207        [bumpsUpdate(o) for o in self.config.values[self.current_fitter_id].keys()]
[ff3b293]208        self.close()
[b0c5e8c]209
210    def onHelp(self):
211        """
212        Show the "Fitting options" section of help
213        """
[e90988c]214        tree_location = GuiUtils.HELP_DIRECTORY_LOCATION
[aed0532]215        tree_location += "/user/qtgui/Perspectives/Fitting/"
[b0c5e8c]216
217        # Actual file anchor will depend on the combo box index
218        # Note that we can be clusmy here, since bad current_fitter_id
219        # will just make the page displayed from the top
220        helpfile = "optimizer.html#fit-" + self.current_fitter_id
221        help_location = tree_location + helpfile
[e90988c]222        webbrowser.open('file://' + os.path.realpath(help_location))
[b0c5e8c]223
224    def widgetFromOption(self, option_id, current_fitter=None):
[2d0e0c1]225        """
226        returns widget's element linked to the given option_id
227        """
[b0c5e8c]228        if current_fitter is None:
229            current_fitter = self.current_fitter_id
[b3e8629]230        if option_id not in list(bumps.options.FIT_FIELDS.keys()): return None
[b0c5e8c]231        option = option_id + '_' + current_fitter
232        if not hasattr(self, option): return None
233        return eval('self.' + option)
[2d0e0c1]234
235    def getResults(self):
236        """
237        Sends back the current choice of parameters
238        """
239        algorithm = self.cbAlgorithm.currentText()
240        return algorithm
241
242    def updateWidgetFromBumps(self, fitter_id):
243        """
244        Given the ID of the current optimizer, fetch the values
245        and update the widget
246        """
247        options = self.config.values[fitter_id]
[b3e8629]248        for option in options.keys():
[2d0e0c1]249            # Find the widget name of the option
250            # e.g. 'samples' for 'dream' is 'self.samples_dream'
251            widget_name = 'self.'+option+'_'+fitter_id
[377ade1]252            if option not in bumps.options.FIT_FIELDS:
253                return
[2d0e0c1]254            if isinstance(bumps.options.FIT_FIELDS[option][1], bumps.options.ChoiceList):
255                control = eval(widget_name)
256                control.setCurrentIndex(control.findText(str(options[option])))
257            else:
258                eval(widget_name).setText(str(options[option]))
259
260        pass
Note: See TracBrowser for help on using the repository browser.