Changeset aca8418 in sasview for src/sas/qtgui/Perspectives


Ignore:
Timestamp:
Jun 6, 2017 7:37:52 AM (7 years ago)
Author:
Piotr Rozyczko <rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
06b0138
Parents:
358b39d
Message:

More polydisp. functionality SASVIEW-601

Location:
src/sas/qtgui/Perspectives/Fitting
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py

    r6964d44 raca8418  
    3737    return (param_name, param_length) 
    3838 
    39 def addParametersToModel(parameters, is2D): 
     39def addParametersToModel(parameters, kernel_module, is2D): 
    4040    """ 
    4141    Update local ModelModel with sasmodel parameters 
     
    6969                if p.name != param.name: 
    7070                    continue 
    71                 item1_2 = QtGui.QStandardItem(str(p.default)) 
     71                width = kernel_module.getParam(p.name+'.width') 
     72                #npts = kernel_module.getParam(p.name+'.npts') 
     73                #nsigs = kernel_module.getParam(p.name+'.nsigmas') 
     74                type = kernel_module.getParam(p.name+'.type') 
     75 
     76                item1_2 = QtGui.QStandardItem(str(width)) 
    7277                item1_2.setEditable(False) 
    73                 item1_3 = QtGui.QStandardItem(str(p.limits[0])) 
     78                item1_3 = QtGui.QStandardItem() 
    7479                item1_3.setEditable(False) 
    75                 item1_4 = QtGui.QStandardItem(str(p.limits[1])) 
     80                item1_4 = QtGui.QStandardItem() 
    7681                item1_4.setEditable(False) 
    77                 item1_5 = QtGui.QStandardItem(p.units) 
     82                item1_5 = QtGui.QStandardItem(type) 
    7883                item1_5.setEditable(False) 
    7984                poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5]) 
     
    156161    model.setHeaderData(5, QtCore.Qt.Horizontal, QtCore.QVariant("Nsigs")) 
    157162    model.setHeaderData(6, QtCore.Qt.Horizontal, QtCore.QVariant("Function")) 
     163 
     164def addErrorPolyHeadersToModel(model): 
     165    """ 
     166    Adds predefined headers to the model 
     167    """ 
     168    model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Parameter")) 
     169    model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("PD[ratio]")) 
     170    model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Error")) 
     171    model.setHeaderData(3, QtCore.Qt.Horizontal, QtCore.QVariant("Min")) 
     172    model.setHeaderData(4, QtCore.Qt.Horizontal, QtCore.QVariant("Max")) 
     173    model.setHeaderData(5, QtCore.Qt.Horizontal, QtCore.QVariant("Npts")) 
     174    model.setHeaderData(6, QtCore.Qt.Horizontal, QtCore.QVariant("Nsigs")) 
     175    model.setHeaderData(7, QtCore.Qt.Horizontal, QtCore.QVariant("Function")) 
    158176 
    159177def addShellsToModel(parameters, model, index): 
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    r358b39d raca8418  
    4646 
    4747# Mapping between column index and relevant parameter name extension 
     48POLY_COL_NAME = 0 
     49POLY_COL_WIDTH = 1 
     50POLY_COL_MIN = 2 
     51POLY_COL_MAX = 3 
     52POLY_COL_NPTS = 4 
     53POLY_COL_NSIGMAS = 5 
     54POLY_COL_FUNCTION = 6 
    4855POLY_COLUMN_DICT = { 
    49     1: 'width', 
    50     2: 'min', 
    51     3: 'max', 
    52     4: 'npts', 
    53     5: 'nsigmas'} 
     56    POLY_COL_WIDTH:  'width', 
     57    POLY_COL_MIN:    'min', 
     58    POLY_COL_MAX:    'max', 
     59    POLY_COL_NPTS:    'npts', 
     60    POLY_COL_NSIGMAS: 'nsigmas'} 
    5461 
    5562class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI): 
     
    175182        self.current_shell_displayed = 0 
    176183        self.has_error_column = False 
     184        self.has_poly_error_column = False 
    177185 
    178186        # signal communicator 
     
    251259        self.setTableProperties(self.lstPoly) 
    252260        # Delegates for custom editing and display 
    253         # self.lstPoly.setItemDelegate(PolyViewDelegate(self)) 
     261        self.lstPoly.setItemDelegate(PolyViewDelegate(self)) 
     262        # Polydispersity function combo response 
     263        self.lstPoly.itemDelegate().combo_updated.connect(self.onPolyComboIndexChange) 
    254264 
    255265        # Magnetism model displayed in magnetism list 
     
    368378        self._model_model.itemChanged.connect(self.updateParamsFromModel) 
    369379        self._poly_model.itemChanged.connect(self.onPolyModelChange) 
     380 
    370381        # TODO after the poly_model prototype accepted 
    371382        #self._magnet_model.itemChanged.connect(self.onMagneticModelChange) 
     
    406417        self.parameters_to_fit = None 
    407418        self.has_error_column = False 
     419        self.has_poly_error_column = False 
    408420 
    409421        self.respondToModelStructure(model=model, structure_factor=None) 
     
    488500        # Extract changed value. Assumes proper validation by QValidator/Delegate 
    489501        # TODO: abstract away hardcoded column numbers 
    490         if model_column == 0: 
     502        if model_column == POLY_COL_NAME: 
    491503            # Is the parameter checked for fitting? 
    492504            value = item.checkState() 
     
    499511                    self.parameters_to_fit.remove(parameter_name) 
    500512            return 
    501         elif model_column == 6: 
     513        elif model_column == POLY_COL_FUNCTION: 
    502514            value = item.text() 
    503515            # TODO: Modify Npts/Nsigs based on function choice 
     516        elif model_column in [POLY_COL_MIN, POLY_COL_MAX]: 
     517            try: 
     518                value = float(item.text()) 
     519            except ValueError: 
     520                # Can't be converted properly, bring back the old value and exit 
     521                return 
     522 
     523            property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()).lower() # Value, min, max, etc. 
     524            # print "%s(%s) => %d" % (parameter_name, property_name, value) 
     525            current_details = self.kernel_module.details[parameter_name] 
     526            current_details[model_column-1] = value 
    504527        else: 
    505528            try: 
     
    509532                return 
    510533 
    511         property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()).lower() # Value, min, max, etc. 
    512         # print "%s(%s) => %d" % (parameter_name, property_name, value) 
    513  
    514         # Update the sasmodel 
    515         # PD[ratio] -> width, npts -> npts, nsigs -> nsigmas 
    516         self.kernel_module.setParam(parameter_name + '.' + POLY_COLUMN_DICT[model_column], value) 
     534            property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()).lower() # Value, min, max, etc. 
     535            # print "%s(%s) => %d" % (parameter_name, property_name, value) 
     536 
     537            # Update the sasmodel 
     538            # PD[ratio] -> width, npts -> npts, nsigs -> nsigmas 
     539            self.kernel_module.setParam(parameter_name + '.' + POLY_COLUMN_DICT[model_column], value) 
    517540 
    518541        # Reload the main model - may not be required if no variable is shown in main view 
     
    633656 
    634657        self.chi2 = res.fitness 
    635         param_list = res.param_list 
    636         param_values = res.pvec 
    637         param_stderr = res.stderr 
     658        param_list = res.param_list # ['radius', 'radius.width'] 
     659        param_values = res.pvec     # array([ 0.36221662,  0.0146783 ]) 
     660        param_stderr = res.stderr   # array([ 1.71293015,  1.71294233]) 
    638661        params_and_errors = zip(param_values, param_stderr) 
    639662        param_dict = dict(izip(param_list, params_and_errors)) 
     
    643666        self.updateModelFromList(param_dict) 
    644667 
     668        self.updatePolyModelFromList(param_dict) 
     669 
    645670        # update charts 
    646671        self.onPlot() 
     
    654679        Take func and throw it inside the model row loop 
    655680        """ 
    656         #assert isinstance(func, function) 
    657681        for row_i in xrange(self._model_model.rowCount()): 
    658682            func(row_i) 
     
    715739        self.has_error_column = True 
    716740 
     741    def updatePolyModelFromList(self, param_dict): 
     742        """ 
     743        Update the polydispersity model with new parameters, create the errors column 
     744        """ 
     745        assert isinstance(param_dict, dict) 
     746        if not dict: 
     747            return 
     748 
     749        def updateFittedValues(row_i): 
     750            # Utility function for main model update 
     751            # internal so can use closure for param_dict 
     752            if row_i >= self._poly_model.rowCount(): 
     753                return 
     754            param_name = str(self._poly_model.item(row_i, 0).text()).rsplit()[-1] + '.width' 
     755            if param_name not in param_dict.keys(): 
     756                return 
     757            # modify the param value 
     758            param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True) 
     759            self._poly_model.item(row_i, 1).setText(param_repr) 
     760            if self.has_poly_error_column: 
     761                error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True) 
     762                self._poly_model.item(row_i, 2).setText(error_repr) 
     763 
     764        def createErrorColumn(row_i): 
     765            # Utility function for error column update 
     766            if row_i >= self._poly_model.rowCount(): 
     767                return 
     768            item = QtGui.QStandardItem() 
     769            for param_name in param_dict.keys(): 
     770                if str(self._poly_model.item(row_i, 0).text()).rsplit()[-1] + '.width' != param_name: 
     771                    continue 
     772                error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True) 
     773                item.setText(error_repr) 
     774            error_column.append(item) 
     775 
     776        # block signals temporarily, so we don't end up 
     777        # updating charts with every single model change on the end of fitting 
     778        self._poly_model.blockSignals(True) 
     779        self.iterateOverModel(updateFittedValues) 
     780        self._poly_model.blockSignals(False) 
     781 
     782        return 
     783 
     784        if self.has_poly_error_column: 
     785            return 
     786 
     787        error_column = [] 
     788        self.iterateOverModel(createErrorColumn) 
     789 
     790        # switch off reponse to model change 
     791        self._poly_model.blockSignals(True) 
     792        self._poly_model.insertColumn(2, error_column) 
     793        self._poly_model.blockSignals(False) 
     794        FittingUtilities.addErrorPolyHeadersToModel(self._poly_model) 
     795 
     796        self.has_poly_error_column = True 
     797 
    717798    def onPlot(self): 
    718799        """ 
     
    9211002 
    9221003        # Update the QModel 
    923         new_rows = FittingUtilities.addParametersToModel(self.model_parameters, self.is2D) 
     1004        new_rows = FittingUtilities.addParametersToModel(self.model_parameters, self.kernel_module, self.is2D) 
     1005 
    9241006        for row in new_rows: 
    9251007            self._model_model.appendRow(row) 
     
    9961078        self._model_model.blockSignals(True) 
    9971079        # Convert to proper indices and set requested enablement 
    998         _ = [self._model_model.item(row, 0).setCheckState(status) for row in rows] 
     1080        [self._model_model.item(row, 0).setCheckState(status) for row in rows] 
    9991081        self._model_model.blockSignals(False) 
    10001082 
     
    11151197        calc_thread.addErrback(self.calculateDataFailed) 
    11161198 
    1117     def calculateDataFailed(self): 
     1199    def calculateDataFailed(self, reason): 
    11181200        """ 
    11191201        Thread returned error 
    11201202        """ 
    1121         print "Calculate Data failed." 
     1203        print "Calculate Data failed with ", reason 
    11221204 
    11231205    def complete1D(self, return_data): 
     
    11571239 
    11581240        # Plot residuals if actual data 
    1159         if self.data_is_loaded: 
    1160             residuals_plot = FittingUtilities.plotResiduals(self.data, fitted_data) 
    1161             residuals_plot.id = "Residual " + residuals_plot.id 
    1162             self.createNewIndex(residuals_plot) 
    1163             self.communicate.plotUpdateSignal.emit([residuals_plot]) 
     1241        if not self.data_is_loaded: 
     1242            return 
     1243 
     1244        residuals_plot = FittingUtilities.plotResiduals(self.data, fitted_data) 
     1245        residuals_plot.id = "Residual " + residuals_plot.id 
     1246        self.createNewIndex(residuals_plot) 
     1247        self.communicate.plotUpdateSignal.emit([residuals_plot]) 
    11641248 
    11651249    def calcException(self, etype, value, tb): 
     
    11971281            return 
    11981282        self._poly_model.clear() 
    1199         for row, param in enumerate(self.model_parameters.form_volume_parameters): 
    1200             # Counters should not be included 
    1201             if not param.polydisperse: 
    1202                 continue 
    1203  
    1204             # Values from the sasmodel 
    1205             width = self.kernel_module.getParam(param.name+'.width') 
    1206             npts = self.kernel_module.getParam(param.name+'.npts') 
    1207             nsigs = self.kernel_module.getParam(param.name+'.nsigmas') 
    1208             # Potential multishell params 
    1209             checked_list = ["Distribution of "+param.name, str(width), 
    1210                             str(param.limits[0]), str(param.limits[1]), 
    1211                             str(npts), str(nsigs), ""] 
    1212             FittingUtilities.addCheckedListToModel(self._poly_model, checked_list) 
    1213  
    1214             # All possible polydisp. functions as strings in combobox 
    1215             func = QtGui.QComboBox() 
    1216             func.addItems([str(name_disp) for name_disp in POLYDISPERSITY_MODELS.iterkeys()]) 
    1217             # Default index 
    1218             func.setCurrentIndex(func.findText(DEFAULT_POLYDISP_FUNCTION)) 
    1219             # Index in the view 
    1220             func_index = self.lstPoly.model().index(row, 6) 
    1221             # Set the combobox in cell 
    1222             self.lstPoly.setIndexWidget(func_index, func) 
    1223  
     1283 
     1284        [self.setPolyModelParameters(row, param) for row, param in \ 
     1285            enumerate(self.model_parameters.form_volume_parameters) if param.polydisperse] 
    12241286        FittingUtilities.addPolyHeadersToModel(self._poly_model) 
    12251287 
     1288    def setPolyModelParameters(self, row, param): 
     1289        """ 
     1290        Creates a checked row of for a polydisperse parameter 
     1291        """ 
     1292        # Values from the sasmodel 
     1293        width = self.kernel_module.getParam(param.name+'.width') 
     1294        npts = self.kernel_module.getParam(param.name+'.npts') 
     1295        nsigs = self.kernel_module.getParam(param.name+'.nsigmas') 
     1296        # Potential multishell params 
     1297        checked_list = ["Distribution of "+param.name, str(width), 
     1298                        str(param.limits[0]), str(param.limits[1]), 
     1299                        str(npts), str(nsigs), "gaussian      "] 
     1300        FittingUtilities.addCheckedListToModel(self._poly_model, checked_list) 
     1301 
     1302        # All possible polydisp. functions as strings in combobox 
     1303        func = QtGui.QComboBox() 
     1304        func.addItems([str(name_disp) for name_disp in POLYDISPERSITY_MODELS.iterkeys()]) 
     1305        # Default index 
     1306        func.setCurrentIndex(func.findText(DEFAULT_POLYDISP_FUNCTION)) 
     1307        # Index in the view 
     1308        func_index = self.lstPoly.model().index(row, 6) 
     1309 
     1310    def onPolyComboIndexChange(self, combo_string, row_index): 
     1311        """ 
     1312        Modify polydisp. defaults on function choice 
     1313        """ 
     1314        # get npts/nsigs for current selection 
     1315        param = self.model_parameters.form_volume_parameters[row_index] 
     1316 
     1317        if combo_string == 'array': 
     1318            try: 
     1319                self.loadPolydispArray() 
     1320            except ValueError: 
     1321                # Don't do anything if file lookup failed 
     1322                return 
     1323            # disable the row 
     1324            [self._poly_model.item(row_index, i).setEnabled(False) for i in xrange(6)] 
     1325            return 
     1326 
     1327        # Enable the row in case it was disabled by Array 
     1328        #[self._poly_model.item(row_index, i).setEnabled(True) for i in xrange(6)] 
     1329 
     1330        npts_index = self._poly_model.index(row_index, POLY_COL_NPTS) 
     1331        nsigs_index = self._poly_model.index(row_index, POLY_COL_NSIGMAS) 
     1332 
     1333        npts = POLYDISPERSITY_MODELS[str(combo_string)].default['npts'] 
     1334        nsigs = POLYDISPERSITY_MODELS[str(combo_string)].default['nsigmas'] 
     1335 
     1336        self._poly_model.setData(npts_index, QtCore.QVariant(npts)) 
     1337        self._poly_model.setData(nsigs_index, QtCore.QVariant(nsigs)) 
     1338 
     1339    def loadPolydispArray(self): 
     1340        """ 
     1341        Show the load file dialog and loads requested data into state 
     1342        """ 
     1343        datafile = QtGui.QFileDialog.getOpenFileName( 
     1344            self, "Choose a weight file", "", "All files (*.*)") 
     1345        if not datafile: 
     1346            logging.info("No weight data chosen.") 
     1347            return 
     1348        values = [] 
     1349        weights = [] 
     1350        with open(datafile, 'r') as column_file: 
     1351            column_data = [line.rstrip().split() for line in column_file.readlines()] 
     1352            for line in column_data: 
     1353                try: 
     1354                    values.append(float(line[0])) 
     1355                    weights.append(float(line[1])) 
     1356                except (ValueError, IndexError): 
     1357                    # just pass through if line with bad data 
     1358                    pass 
     1359 
     1360        self.disp_model = POLYDISPERSITY_MODELS['array']() 
     1361        self.disp_model.set_weights(np.array(values), np.array(weights)) 
     1362 
    12261363    def setMagneticModel(self): 
    12271364        """ 
     
    12311368            return 
    12321369        self._magnet_model.clear() 
    1233         for param in self.model_parameters.call_parameters: 
    1234             if param.type != "magnetic": 
    1235                 continue 
    1236             checked_list = [param.name, 
    1237                             str(param.default), 
    1238                             str(param.limits[0]), 
    1239                             str(param.limits[1]), 
    1240                             param.units] 
    1241             FittingUtilities.addCheckedListToModel(self._magnet_model, checked_list) 
    1242  
     1370        [self.addCheckedMagneticListToModel(param, self._magnet_model) for param in \ 
     1371            self.model_parameters.call_parameters if param != 'magnetic'] 
    12431372        FittingUtilities.addHeadersToModel(self._magnet_model) 
     1373 
     1374    def addCheckedMagneticListToModel(self, param, model): 
     1375        """ 
     1376        Wrapper for model update with a subset of magnetic parameters 
     1377        """ 
     1378        checked_list = [param.name, 
     1379                        str(param.default), 
     1380                        str(param.limits[0]), 
     1381                        str(param.limits[1]), 
     1382                        param.units] 
     1383 
     1384        FittingUtilities.addCheckedListToModel(model, checked_list) 
    12441385 
    12451386    def enableStructureFactorControl(self, structure_factor): 
  • src/sas/qtgui/Perspectives/Fitting/UI/FittingWidgetUI.ui

    r00b3b40 raca8418  
    402402            </property> 
    403403            <property name="selectionMode"> 
    404              <enum>QAbstractItemView::MultiSelection</enum> 
     404             <enum>QAbstractItemView::ExtendedSelection</enum> 
    405405            </property> 
    406406            <property name="selectionBehavior"> 
  • src/sas/qtgui/Perspectives/Fitting/ViewDelegate.py

    rdc5ef15 raca8418  
    2121POLY_NSIGS=5 
    2222POLY_FUNCTION=6 
     23POLY_EDITABLE_PARAMS = [POLY_MIN, POLY_MAX, POLY_NPTS, POLY_NSIGS] 
    2324 
    2425 
     
    9495    Custom delegate for appearance and behavior control of the polydispersity view 
    9596    """ 
     97    combo_updated = QtCore.pyqtSignal(str, int) 
    9698    def createEditor(self, widget, option, index): 
    9799        # Remember the current choice 
    98100        current_text = index.data().toString() 
     101        if not index.isValid(): 
     102            return 0 
    99103        if index.column() == POLY_FUNCTION: 
    100104            editor = QtGui.QComboBox(widget) 
     
    102106                editor.addItem(function) 
    103107            current_index = editor.findText(current_text) 
    104             editor.setCurrentIndex(current_index if current_index>-1 else 0) 
     108            editor.setCurrentIndex(current_index if current_index>-1 else 3) 
     109            editor.currentIndexChanged.connect(lambda: self.combo_updated.emit(str(editor.currentText()), index.row())) 
     110            return editor 
     111        elif index.column() in POLY_EDITABLE_PARAMS: 
     112            editor = QtGui.QLineEdit(widget) 
     113            validator = QtGui.QDoubleValidator() 
     114            editor.setValidator(validator) 
    105115            return editor 
    106116        else: 
    107117            QtGui.QStyledItemDelegate.createEditor(self, widget, option, index) 
     118 
     119    def paint(self, painter, option, index): 
     120        """ 
     121        Overwrite generic painter for certain columns 
     122        """ 
     123        if index.column() in (POLY_MIN, POLY_MAX): 
     124            # Units - present in nice HTML 
     125            options = QtGui.QStyleOptionViewItemV4(option) 
     126            self.initStyleOption(options,index) 
     127 
     128            style = QtGui.QApplication.style() if options.widget is None else options.widget.style() 
     129 
     130            # Prepare document for inserting into cell 
     131            doc = QtGui.QTextDocument() 
     132 
     133            # Convert the unit description into HTML 
     134            text_html = GuiUtils.convertUnitToHTML(str(options.text)) 
     135            doc.setHtml(text_html) 
     136 
     137            # delete the original content 
     138            options.text = "" 
     139            style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter, options.widget); 
     140 
     141            context = QtGui.QAbstractTextDocumentLayout.PaintContext() 
     142            textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, options) 
     143 
     144            painter.save() 
     145            painter.translate(textRect.topLeft()) 
     146            painter.setClipRect(textRect.translated(-textRect.topLeft())) 
     147            # Draw the QTextDocument in the cell 
     148            doc.documentLayout().draw(painter, context) 
     149            painter.restore() 
     150        else: 
     151            # Just the default paint 
     152            QtGui.QStyledItemDelegate.paint(self, painter, option, index) 
Note: See TracChangeset for help on using the changeset viewer.