source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingOptions.py @ 3b8cc00

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 3b8cc00 was e90988c, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Show help pages in default browser. Fixed some help links and modified unit tests. SASVIEW-800

  • Property mode set to 100644
File size: 7.6 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)
43
44        self.config = config
45
46        # no reason to have this widget resizable
47        self.setFixedSize(self.minimumSizeHint())
48
[85487ebd]49        self.setWindowTitle("Fit Algorithms")
[2d0e0c1]50
51        # Fill up the algorithm combo, based on what BUMPS says is available
52        self.cbAlgorithm.addItems([n.name for n in fitters.FITTERS if n.id in fitters.FIT_ACTIVE_IDS])
53
54        # Handle the Apply button click
[4992ff2]55        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onApply)
[b0c5e8c]56        # handle the Help button click
[4992ff2]57        self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self.onHelp)
[2d0e0c1]58
59        # Handle the combo box changes
60        self.cbAlgorithm.currentIndexChanged.connect(self.onAlgorithmChange)
61
62        # Set the default index
63        default_name = [n.name for n in fitters.FITTERS if n.id == fitters.FIT_DEFAULT_ID][0]
64        default_index = self.cbAlgorithm.findText(default_name)
65        self.cbAlgorithm.setCurrentIndex(default_index)
[b0c5e8c]66
67        # Assign appropriate validators
68        self.assignValidators()
69
[72f4834]70        # Set defaults
[2d0e0c1]71        self.current_fitter_id = fitters.FIT_DEFAULT_ID
72
[72f4834]73        # OK has to be initialized to True, after initial validator setup
[4992ff2]74        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
[72f4834]75
[b0c5e8c]76    def assignValidators(self):
77        """
78        Use options.FIT_FIELDS to assert which line edit gets what validator
79        """
[b3e8629]80        for option in bumps.options.FIT_FIELDS.keys():
[b0c5e8c]81            (f_name, f_type) = bumps.options.FIT_FIELDS[option]
82            validator = None
83            if type(f_type) == types.FunctionType:
84                validator = QtGui.QIntValidator()
[72f4834]85                validator.setBottom(0)
[b3e8629]86            elif f_type == float:
[d6b8a1d]87                validator = GuiUtils.DoubleValidator()
[72f4834]88                validator.setBottom(0)
[b0c5e8c]89            else:
90                continue
91            for fitter_id in fitters.FIT_ACTIVE_IDS:
92                line_edit = self.widgetFromOption(str(option), current_fitter=str(fitter_id))
93                if hasattr(line_edit, 'setValidator') and validator is not None:
94                    line_edit.setValidator(validator)
[72f4834]95                    line_edit.textChanged.connect(self.check_state)
96                    line_edit.textChanged.emit(line_edit.text())
97
98    def check_state(self, *args, **kwargs):
99        sender = self.sender()
100        validator = sender.validator()
101        state = validator.validate(sender.text(), 0)[0]
102        if state == QtGui.QValidator.Acceptable:
103            color = '' # default
[4992ff2]104            self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
[72f4834]105        else:
106            color = '#fff79a' # yellow
[4992ff2]107            self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
[72f4834]108
109        sender.setStyleSheet('QLineEdit { background-color: %s }' % color)
[2d0e0c1]110
111    def onAlgorithmChange(self, index):
112        """
113        Change the page in response to combo box index
114        """
115        # Find the algorithm ID from name
116        self.current_fitter_id = \
117            [n.id for n in fitters.FITTERS if n.name == str(self.cbAlgorithm.currentText())][0]
118
119        # find the right stacked widget
120        widget_name = "self.page_"+str(self.current_fitter_id)
121
122        # Convert the name into widget instance
123        widget_to_activate = eval(widget_name)
[b0c5e8c]124        index_for_this_id = self.stackedWidget.indexOf(widget_to_activate)
[2d0e0c1]125
126        # Select the requested widget
[b0c5e8c]127        self.stackedWidget.setCurrentIndex(index_for_this_id)
[2d0e0c1]128
129        self.updateWidgetFromBumps(self.current_fitter_id)
130
[b0c5e8c]131        self.assignValidators()
132
[b67bfa7]133        # OK has to be reinitialized to True
[4992ff2]134        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
[b67bfa7]135
[2d0e0c1]136    def onApply(self):
137        """
[b0c5e8c]138        Update the fitter object
[2d0e0c1]139        """
[b0c5e8c]140        # Notify the perspective, so the window title is updated
141        self.fit_option_changed.emit(self.cbAlgorithm.currentText())
[2d0e0c1]142
[b0c5e8c]143        def bumpsUpdate(option):
144            """
145            Utility method for bumps state update
146            """
[2d0e0c1]147            widget = self.widgetFromOption(option)
[4992ff2]148            new_value = widget.currentText() if isinstance(widget, QtWidgets.QComboBox) \
[b0c5e8c]149                else float(widget.text())
[2d0e0c1]150            self.config.values[self.current_fitter_id][option] = new_value
151
[b0c5e8c]152        # Update the BUMPS singleton
[b3e8629]153        [bumpsUpdate(o) for o in self.config.values[self.current_fitter_id].keys()]
[b0c5e8c]154
155    def onHelp(self):
156        """
157        Show the "Fitting options" section of help
158        """
[e90988c]159        tree_location = GuiUtils.HELP_DIRECTORY_LOCATION
160        tree_location += "/user/sasgui/perspectives/fitting/"
[b0c5e8c]161
162        # Actual file anchor will depend on the combo box index
163        # Note that we can be clusmy here, since bad current_fitter_id
164        # will just make the page displayed from the top
165        helpfile = "optimizer.html#fit-" + self.current_fitter_id
166        help_location = tree_location + helpfile
[e90988c]167        webbrowser.open('file://' + os.path.realpath(help_location))
[b0c5e8c]168
169    def widgetFromOption(self, option_id, current_fitter=None):
[2d0e0c1]170        """
171        returns widget's element linked to the given option_id
172        """
[b0c5e8c]173        if current_fitter is None:
174            current_fitter = self.current_fitter_id
[b3e8629]175        if option_id not in list(bumps.options.FIT_FIELDS.keys()): return None
[b0c5e8c]176        option = option_id + '_' + current_fitter
177        if not hasattr(self, option): return None
178        return eval('self.' + option)
[2d0e0c1]179
180    def getResults(self):
181        """
182        Sends back the current choice of parameters
183        """
184        algorithm = self.cbAlgorithm.currentText()
185        return algorithm
186
187    def updateWidgetFromBumps(self, fitter_id):
188        """
189        Given the ID of the current optimizer, fetch the values
190        and update the widget
191        """
192        options = self.config.values[fitter_id]
[b3e8629]193        for option in options.keys():
[2d0e0c1]194            # Find the widget name of the option
195            # e.g. 'samples' for 'dream' is 'self.samples_dream'
196            widget_name = 'self.'+option+'_'+fitter_id
[377ade1]197            if option not in bumps.options.FIT_FIELDS:
198                return
[2d0e0c1]199            if isinstance(bumps.options.FIT_FIELDS[option][1], bumps.options.ChoiceList):
200                control = eval(widget_name)
201                control.setCurrentIndex(control.findText(str(options[option])))
202            else:
203                eval(widget_name).setText(str(options[option]))
204
205        pass
Note: See TracBrowser for help on using the repository browser.