source: sasview/src/sas/qtgui/Utilities/AddMultEditor.py @ 01ef3f7

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 01ef3f7 was 01ef3f7, checked in by celinedurniak <celine.durniak@…>, 6 years ago

Implemented new GUI for Add/Multiply? models

  • Property mode set to 100644
File size: 9.9 KB
RevLine 
[01ef3f7]1"""
2Widget for simple add / multiply editor.
3"""
4# numpy methods required for the validator! Don't remove.
5# pylint: disable=unused-import,unused-wildcard-import,redefined-builtin
6from numpy import *
7
8from PyQt5 import QtCore
9from PyQt5 import QtGui
10from PyQt5 import QtWidgets
11import webbrowser
12
13import os
14import numpy as np
15import re
16import logging
17import sas.qtgui.Utilities.GuiUtils as GuiUtils
18from sasmodels.sasview_model import load_standard_models
19from sas.qtgui.Perspectives.Fitting import ModelUtilities
20
21# Local UI
22from sas.qtgui.Utilities.UI.AddMultEditorUI import Ui_AddMultEditorUI
23
24# Template for the output plugin file
25SUM_TEMPLATE = """
26from sasmodels.core import load_model_info
27from sasmodels.sasview_model import make_model_from_info
28
29model_info = load_model_info('{model1}{operator}{model2}')
30model_info.name = '{name}'
31model_info.description = '{desc_line}'
32Model = make_model_from_info(model_info)
33"""
34
35# Color of backgrounds to underline valid or invalid input
36BG_WHITE = "background-color: rgb(255, 255, 255);"
37BG_RED = "background-color: rgb(244, 170, 164);"
38
39
40class AddMultEditor(QtWidgets.QDialog, Ui_AddMultEditorUI):
41    """
42       Dialog for easy custom composite models.  Provides a Dialog panel
43       to choose two existing models (including pre-existing Plugin Models which
44       may themselves be composite models) as well as an operation on those models
45       (add or multiply) the resulting model will add a scale parameter and a
46       background parameter.
47       The user also gives a brief help for the model in a description box and
48       must provide a unique name which is verified as unique before the new
49    """
50    def __init__(self, parent=None):
51        super(AddMultEditor, self).__init__()
52
53        self.parent = parent
54
55        self.setupUi(self)
56
57        #  uncheck self.chkOverwrite
58        self.chkOverwrite.setChecked(False)
59
60        # Disabled Apply button until input of valid output plugin name
61        self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
62
63        # Flag for correctness of resulting name
64        self.good_name = False
65
66        self.setupSignals()
67
68        self.list_models = self.readModels()
69
70        # Fill models' comboboxes
71        self.setupModels()
72
73        self.setFixedSize(self.minimumSizeHint())
74
75        # Name and directory for saving new plugin model
76        self.plugin_filename = None
77        self.plugin_dir = ModelUtilities.find_plugins_dir()
78
79        # Validators
80        rx = QtCore.QRegExp("^[A-Za-z0-9_]*$")
81        txt_validator = QtGui.QRegExpValidator(rx)
82        self.txtName.setValidator(txt_validator)
83
84    def setupModels(self):
85        """ Add list of models to 'Model1' and 'Model2' comboboxes """
86        # Load the model dict
87        self.cbModel1.addItems(self.list_models)
88        self.cbModel2.addItems(self.list_models)
89
90        # set the default initial value of Model1 and Model2
91        index_ini_model1 = self.cbModel1.findText('sphere', QtCore.Qt.MatchFixedString)
92
93        if index_ini_model1 >= 0:
94            self.cbModel1.setCurrentIndex(index_ini_model1)
95        else:
96            self.cbModel1.setCurrentIndex(0)
97
98        index_ini_model2 = self.cbModel2.findText('cylinder',
99                                                  QtCore.Qt.MatchFixedString)
100        if index_ini_model2 >= 0:
101            self.cbModel2.setCurrentIndex(index_ini_model2)
102        else:
103            self.cbModel2.setCurrentIndex(0)
104
105    def readModels(self):
106        """ Generate list of models """
107        models = load_standard_models()
108        models_dict = {}
109        for model in models:
110            models_dict[model.name] = model
111
112        return sorted([model_name for model_name in models_dict])
113
114    def setupSignals(self):
115        """ Signals from various elements """
116        # check existence of output filename when entering name
117        # or when overwriting not allowed
118        self.txtName.editingFinished.connect(self.onNameCheck)
119        self.chkOverwrite.stateChanged.connect(self.onNameCheck)
120
121        self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.onApply)
122        self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self.onHelp)
123        self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self.close)
124
125        # change displayed equation when changing operator
126        self.cbOperator.currentIndexChanged.connect(self.onOperatorChange)
127
128    def onNameCheck(self):
129        """
130        Check if proposed new model name does not already exists
131        (if the overwriting is not allowed).
132        If not an error message not show error message is displayed
133        """
134
135        # Get the function/file name
136        title = self.txtName.text().lstrip().rstrip()
137
138        filename = title + '.py'
139
140        if self.chkOverwrite.isChecked():
141            # allow overwriting -> only valid name needs to be checked
142            # (done with validator in __init__ above)
143            self.good_name = True
144            self.txtName.setStyleSheet(BG_WHITE)
145            self.plugin_filename = os.path.join(self.plugin_dir, filename)
146        else:
147            # No overwriting -> check existence of filename
148            # Create list of existing model names for comparison
149            # fake existing regular model name list
150            models_list = [item + '.py' for item in self.list_models]
151            if filename in models_list:
152                self.good_name = False
153                self.txtName.setStyleSheet(BG_RED)
154                msg = "Plugin with specified name already exists.\n"
155                msg += "Please specify different filename or allow file overwrite."
156                logging.warning(msg)
157                QtWidgets.QMessageBox.critical(self, 'Plugin Error', msg)
158            else:
159                s_title = title
160                if len(title) > 20:
161                    s_title = title[0:19] + '...'
162                logging.info("Model function ({}) has been set!\n".
163                                format(str(s_title)))
164                self.good_name = True
165                self.txtName.setStyleSheet(BG_WHITE)
166                self.plugin_filename = os.path.join(self.plugin_dir, filename)
167
168        # Enable Apply push button only if valid name
169        self.buttonBox.button(
170            QtWidgets.QDialogButtonBox.Apply).setEnabled(self.good_name)
171
172        return self.good_name
173
174    def onOperatorChange(self, index):
175        """ Respond to operator combo box changes """
176
177        self.lblEquation.setText("Plugin_model = scale_factor * "
178                                 "(model_1 {} model_2) + background".
179                                 format(self.cbOperator.currentText()))
180
181    def onApply(self):
182        """ Validity check, save model to file """
183
184        # if name OK write file and test
185        self.buttonBox.button(
186            QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
187
188        self.write_new_model_to_file(self.plugin_filename,
189                                     self.cbModel1.currentText(),
190                                     self.cbModel2.currentText(),
191                                     self.cbOperator.currentText())
192
193        success = self.checkModel(self.plugin_filename)
194
195        if success:
196            # Update list of models in FittingWidget and AddMultEditor
197            self.parent.communicate.customModelDirectoryChanged.emit()
198            self.updateModels()
199
200    def write_new_model_to_file(self, fname, model1_name, model2_name, operator):
201        """ Write and Save file """
202
203        description = self.txtDescription.text().lstrip().rstrip()
204        if description.strip() != '':
205            # Sasmodels generates a description for us. If the user provides
206            # their own description, add a line to overwrite the sasmodels one
207            desc_line = description
208        else:
209            desc_line = "{} {} {}".format(model1_name,
210                                          operator,
211                                          model2_name)
212
213        name = os.path.splitext(os.path.basename(fname))[0]
214        output = SUM_TEMPLATE.format(name=name,
215                                     model1=model1_name,
216                                     model2=model2_name,
217                                     operator=operator,
218                                     desc_line=desc_line)
219
220        with open(fname, 'w') as out_f:
221            out_f.write(output)
222
223    # same version as in TabbedModelEditor
224    @classmethod
225    def checkModel(cls, path):
226        """ Check that the model saved in file 'path' can run. """
227
228        # try running the model
229        from sasmodels.sasview_model import load_custom_model
230        Model = load_custom_model(path)
231        model = Model()
232        q = np.array([0.01, 0.1])
233        _ = model.evalDistribution(q)
234        qx, qy = np.array([0.01, 0.01]), np.array([0.1, 0.1])
235        _ = model.evalDistribution([qx, qy])
236        # check the model's unit tests run
237        from sasmodels.model_test import run_one
238        # TODO see comments in TabbedModelEditor
239        # result = run_one(path)
240        result = True  # to be commented out
241        return result
242
243    def updateModels(self):
244        """ Update contents of comboboxes with new plugin models """
245
246        self.cbModel1.blockSignals(True)
247        self.cbModel1.clear()
248        self.cbModel1.blockSignals(False)
249
250        self.cbModel2.blockSignals(True)
251        self.cbModel2.clear()
252        self.cbModel2.blockSignals(False)
253        # Retrieve the list of models
254        model_list = self.readModels()
255        # Populate the models comboboxes
256        self.cbModel1.addItems(model_list)
257        self.cbModel2.addItems(model_list)
258
259    def onHelp(self):
260        """ Display related help section """
261
262        try:
263            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
264                            "/user/sasgui/perspectives/fitting/fitting_help.html#sum-multi-p1-p2"
265            webbrowser.open('file://' + os.path.realpath(help_location))
266        except AttributeError:
267            # No manager defined - testing and standalone runs
268            pass
Note: See TracBrowser for help on using the repository browser.