source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @ 86f88d1

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

Redo the parameter table view as treeview to enable additional polydispersity data display

  • Property mode set to 100755
File size: 19.7 KB
Line 
1import sys
2import json
3import  os
4from collections import defaultdict
5
6from PyQt4 import QtGui
7from PyQt4 import QtCore
8
9from UI.FittingWidgetUI import Ui_FittingWidgetUI
10
11from sasmodels import generate
12from sasmodels import modelinfo
13from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
14
15TAB_MAGNETISM = 4
16TAB_POLY = 3
17CATEGORY_DEFAULT="Choose category..."
18
19class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI):
20    """
21    Main widget for selecting form and structure factor models
22    """
23    def __init__(self, manager=None, parent=None, data=None):
24        """
25
26        :param manager:
27        :param parent:
28        :return:
29        """
30        super(FittingWidget, self).__init__()
31
32        # Necessary globals
33        self.model_is_loaded = False
34        self._data = data
35        self.is2D = False
36        self.model_has_shells = False
37        self.data_assigned = False
38        self._previous_category_index = 0
39        self._last_model_row = 0
40
41        # Main GUI setup up
42        self.setupUi(self)
43        self.setWindowTitle("Fitting")
44
45        # Set the main models
46        self._model_model = QtGui.QStandardItemModel()
47        self._poly_model = QtGui.QStandardItemModel()
48        self._magnet_model = QtGui.QStandardItemModel()
49        # Proxy model for custom views on the main _model_model
50        self.proxyModel = QtGui.QSortFilterProxyModel()
51
52        # Param model displayed in param list
53        self.lstParams.setModel(self._model_model)
54        self._readCategoryInfo()
55        self.model_parameters = None
56        self.lstParams.setAlternatingRowColors(True)
57
58        # Poly model displayed in poly list
59        self.lstPoly.setModel(self._poly_model)
60        self.setPolyModel()
61        self.setTableProperties(self.lstPoly)
62
63        # Magnetism model displayed in magnetism list
64        self.lstMagnetic.setModel(self._magnet_model)
65        self.setMagneticModel()
66        self.setTableProperties(self.lstMagnetic)
67
68        # Defaults for the strcutre factors
69        self.setDefaultStructureCombo()
70
71        # make structure factor and model CBs disabled
72        self.disableModelCombo()
73        self.disableStructureCombo()
74
75        # Generate the category list for display
76        category_list = sorted(self.master_category_dict.keys())
77        self.cbCategory.addItem(CATEGORY_DEFAULT)
78        self.cbCategory.addItems(category_list)
79        self.cbCategory.addItem("Structure Factor")
80        self.cbCategory.setCurrentIndex(0)
81
82        # Connect signals to controls
83        self.initializeSignals()
84
85        # Initial control state
86        self.initializeControls()
87
88    @property
89    def data(self):
90        return self._data
91
92    @data.setter
93    def data(self, value):
94        """ data setter """
95        self._data = value
96        self.data_assigned = True
97        # TODO: update ranges, chi2 etc
98
99    def acceptsData(self):
100        """ Tells the caller this widget can accept new dataset """
101        return not self.data_assigned
102
103    def disableModelCombo(self):
104        self.cbModel.setEnabled(False)
105        self.label_3.setEnabled(False)
106
107    def enableModelCombo(self):
108        self.cbModel.setEnabled(True)
109        self.label_3.setEnabled(True)
110
111    def disableStructureCombo(self):
112        self.cbStructureFactor.setEnabled(False)
113        self.label_4.setEnabled(False)
114
115    def enableStructureCombo(self):
116        self.cbStructureFactor.setEnabled(True)
117        self.label_4.setEnabled(True)
118
119    def initializeControls(self):
120        """
121        Set initial control enablement
122        """
123        self.cmdFit.setEnabled(False)
124        self.cmdPlot.setEnabled(True)
125        self.chkPolydispersity.setEnabled(True)
126        self.chkPolydispersity.setCheckState(False)
127        self.chk2DView.setEnabled(True)
128        self.chk2DView.setCheckState(False)
129        self.chkMagnetism.setEnabled(False)
130        self.chkMagnetism.setCheckState(False)
131        # tabs
132        self.tabFitting.setTabEnabled(TAB_POLY, False)
133        self.tabFitting.setTabEnabled(TAB_MAGNETISM, False)
134        # set initial labels
135        self.lblMinRangeDef.setText("---")
136        self.lblMaxRangeDef.setText("---")
137        self.lblChi2Value.setText("---")
138
139    def initializeSignals(self):
140        """
141        Connect GUI element signals
142        """
143        self.cbStructureFactor.currentIndexChanged.connect(self.selectStructureFactor)
144        self.cbCategory.currentIndexChanged.connect(self.selectCategory)
145        self.cbModel.currentIndexChanged.connect(self.selectModel)
146        self.chk2DView.toggled.connect(self.toggle2D)
147        self.chkPolydispersity.toggled.connect(self.togglePoly)
148        self.chkMagnetism.toggled.connect(self.toggleMagnetism)
149
150    def setDefaultStructureCombo(self):
151        # Fill in the structure factors combo box with defaults
152        structure_factor_list = self.master_category_dict.pop('Structure Factor')
153        structure_factors = []
154        self.cbStructureFactor.clear()
155        for (structure_factor, _) in structure_factor_list:
156            structure_factors.append(structure_factor)
157        self.cbStructureFactor.addItems(sorted(structure_factors))
158
159    def selectCategory(self):
160        """
161        Select Category from list
162        :return:
163        """
164        category = self.cbCategory.currentText()
165        # Check if the user chose "Choose category entry"
166        if str(category) == CATEGORY_DEFAULT:
167            # if the previous category was not the default, keep it.
168            # Otherwise, just return
169            if self._previous_category_index != 0:
170                self.cbCategory.setCurrentIndex(self._previous_category_index)
171            return
172
173        if category == "Structure Factor":
174            self.disableModelCombo()
175            self.enableStructureCombo()
176            return
177
178        self.cbModel.blockSignals(True)
179        self.cbModel.clear()
180        self.cbModel.blockSignals(False)
181        self.enableModelCombo()
182        self.disableStructureCombo()
183
184        self._previous_category_index = self.cbCategory.currentIndex()
185        model_list = self.master_category_dict[str(category)]
186        models = []
187        for (model, _) in model_list:
188            models.append(model)
189        self.cbModel.addItems(sorted(models))
190
191    def selectModel(self):
192        """
193        Select Model from list
194        :return:
195        """
196        model = self.cbModel.currentText()
197        self.setModelModel(model)
198
199    def selectStructureFactor(self):
200        """
201        Select Structure Factor from list
202        :param:
203        :return:
204        """
205        model = self.cbStructureFactor.currentText()
206        self.setModelModel(model)
207
208    def _readCategoryInfo(self):
209        """
210        Reads the categories in from file
211        """
212        self.master_category_dict = defaultdict(list)
213        self.by_model_dict = defaultdict(list)
214        self.model_enabled_dict = defaultdict(bool)
215
216        try:
217            categorization_file = CategoryInstaller.get_user_file()
218            if not os.path.isfile(categorization_file):
219                categorization_file = CategoryInstaller.get_default_file()
220            cat_file = open(categorization_file, 'rb')
221            self.master_category_dict = json.load(cat_file)
222            self._regenerate_model_dict()
223            cat_file.close()
224        except IOError:
225            raise
226            print 'Problem reading in category file.'
227            print 'We even looked for it, made sure it was there.'
228            print 'An existential crisis if there ever was one.'
229
230    def _regenerate_model_dict(self):
231        """
232        regenerates self.by_model_dict which has each model name as the
233        key and the list of categories belonging to that model
234        along with the enabled mapping
235        """
236        self.by_model_dict = defaultdict(list)
237        for category in self.master_category_dict:
238            for (model, enabled) in self.master_category_dict[category]:
239                self.by_model_dict[model].append(category)
240                self.model_enabled_dict[model] = enabled
241
242    def getIterParams(self, model):
243        """
244        Returns a list of all multi-shell parameters in 'model'
245        """
246        iter_params = list(filter(lambda par: "[" in par.name, model.iq_parameters))
247
248        return iter_params
249
250    def getMultiplicity(self, model):
251        """
252        Finds out if 'model' has multishell parameters.
253        If so, returns the name of the counter parameter and the number of shells
254        """
255        iter_param = ""
256        iter_length = 0
257
258        iter_params = self.getIterParams(model)
259        # pull out the iterator parameter name and length
260        if iter_params:
261            iter_length = iter_params[0].length
262            iter_param = iter_params[0].length_control
263        return (iter_param, iter_length)
264
265    def addBackgroundToModel(self, model):
266        """
267        Adds background parameter with default values to the model
268        """
269        assert(isinstance(model, QtGui.QStandardItemModel))
270
271        checked_list = ['background', '0.001', '-inf', 'inf', '1/cm']
272        self.addCheckedListToModel(model, checked_list)
273
274    def addScaleToModel(self, model):
275        """
276        Adds scale parameter with default values to the model
277        """
278        assert(isinstance(model, QtGui.QStandardItemModel))
279
280        checked_list = ['scale', '1.0', '0.0', 'inf', '']
281        self.addCheckedListToModel(model, checked_list)
282
283    def addCheckedListToModel(self, model, param_list):
284        assert(isinstance(model, QtGui.QStandardItemModel))
285        item_list = [QtGui.QStandardItem(item) for item in param_list]
286        item_list[0].setCheckable(True)
287        model.appendRow(item_list)
288
289    def addHeadersToModel(self, model):
290        """
291        Adds predefined headers to the model
292        """
293        model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Parameter"))
294        model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("Value"))
295        model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Min"))
296        model.setHeaderData(3, QtCore.Qt.Horizontal, QtCore.QVariant("Max"))
297        model.setHeaderData(4, QtCore.Qt.Horizontal, QtCore.QVariant("[Units]"))
298
299    def addPolyHeadersToModel(self, model):
300        """
301        Adds predefined headers to the model
302        """
303        model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Parameter"))
304        model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("PD[ratio]"))
305        model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Min"))
306        model.setHeaderData(3, QtCore.Qt.Horizontal, QtCore.QVariant("Max"))
307        model.setHeaderData(4, QtCore.Qt.Horizontal, QtCore.QVariant("Npts"))
308        model.setHeaderData(5, QtCore.Qt.Horizontal, QtCore.QVariant("Nsigs"))
309        model.setHeaderData(6, QtCore.Qt.Horizontal, QtCore.QVariant("Function"))
310
311    def setModelModel(self, model_name):
312        """
313        Setting model parameters into table based on selected
314        :param model_name:
315        :return:
316        """
317        # Crete/overwrite model items
318        self._model_model.clear()
319        model_name = str(model_name)
320        kernel_module = generate.load_kernel_module(model_name)
321        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', []))
322
323        #TODO: scale and background are implicit in sasmodels and needs to be added
324        self.addScaleToModel(self._model_model)
325        self.addBackgroundToModel(self._model_model)
326
327        self.addParametersToModel(self.model_parameters, self._model_model)
328
329        self.addHeadersToModel(self._model_model)
330
331        self.addExtraShells()
332
333        self.setPolyModel()
334        self.setMagneticModel()
335        self.model_is_loaded = True
336
337    def addParametersToModel(self, parameters, model):
338        """
339        Update local ModelModel with sasmodel parameters
340        """
341        multishell_parameters = self.getIterParams(parameters)
342        multishell_param_name, _ = self.getMultiplicity(parameters)
343
344        #TODO: iq_parameters are used here. If orientation paramateres or magnetic are needed
345        # kernel_paramters should be used instead
346        for param in parameters.iq_parameters:
347            # don't include shell parameters
348            if param.name == multishell_param_name:
349                continue
350            # Modify parameter name from <param>[n] to <param>1
351            item_name = param.name
352            if param in multishell_parameters:
353                item_name = self.replaceShellName(param.name, 1)
354
355            item1 = QtGui.QStandardItem(item_name)
356            item1.setCheckable(True)
357            # check for polydisp params
358            if param.polydisperse:
359                poly_item = QtGui.QStandardItem("Polydispersity")
360                item1_1 = QtGui.QStandardItem("Distribution")
361                # Find param in volume_params
362                for p in parameters.form_volume_parameters:
363                    if p.name == param.name:
364                        item1_2 = QtGui.QStandardItem(str(p.default))
365                        item1_3 = QtGui.QStandardItem(str(p.limits[0]))
366                        item1_4 = QtGui.QStandardItem(str(p.limits[1]))
367                        item1_5 = QtGui.QStandardItem(p.units)
368                        poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
369                        break
370
371                item1.appendRow([poly_item])
372
373            item2 = QtGui.QStandardItem(str(param.default))
374            item3 = QtGui.QStandardItem(str(param.limits[0]))
375            item4 = QtGui.QStandardItem(str(param.limits[1]))
376            item5 = QtGui.QStandardItem(param.units)
377            model.appendRow([item1, item2, item3, item4, item5])
378
379        self._last_model_row = self._model_model.rowCount()
380
381    def modelToFittingParameters(self):
382        """
383        Prepare the fitting data object, based on current ModelModel
384        """
385        pass
386
387    def replaceShellName(self, param_name, value):
388        """
389        Updates parameter name from <param_name>[n_shell] to <param_name>value
390        """
391        assert('[' in param_name)
392        return param_name[:param_name.index('[')]+str(value)
393
394    def setTableProperties(self, table):
395        """
396        Setting table properties
397        :param table:
398        :return:
399        """
400        # Table properties
401        table.verticalHeader().setVisible(False)
402        table.setAlternatingRowColors(True)
403        table.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
404        table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
405        table.resizeColumnsToContents()
406
407        # Header
408        header = table.horizontalHeader()
409        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
410
411        header.ResizeMode(QtGui.QHeaderView.Interactive)
412        header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
413        header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents)
414
415    def setPolyModel(self):
416        """
417        Set polydispersity values
418        """
419        ### TODO: apply proper proxy model filtering from the main _model_model
420
421        if not self.model_parameters:
422            return
423        self._poly_model.clear()
424        for row, param in enumerate(self.model_parameters.form_volume_parameters):
425            # Counters should not be included
426            if not param.polydisperse:
427                continue
428
429            # Potential multishell params
430            #length_control = param.length_control
431            #if length_control in param.name:
432
433            checked_list = ["Distribution of "+param.name, str(param.default),
434                            str(param.limits[0]),str(param.limits[1]),
435                            "35", "3", ""]
436            self.addCheckedListToModel(self._poly_model, checked_list)
437
438            #TODO: Need to find cleaner way to input functions
439            func = QtGui.QComboBox()
440            func.addItems(['rectangle','array','lognormal','gaussian','schulz',])
441            func_index = self.lstPoly.model().index(row,6)
442            self.lstPoly.setIndexWidget(func_index,func)
443
444        self.addPolyHeadersToModel(self._poly_model)
445
446    def setMagneticModel(self):
447        """
448        Set magnetism values on model
449        """
450        if not self.model_parameters:
451            return
452        self._magnet_model.clear()
453        for param in self.model_parameters.call_parameters:
454            if param.type != "magnetic":
455                continue
456            checked_list = [param.name,
457                            str(param.default),
458                            str(param.limits[0]),
459                            str(param.limits[1]),
460                            param.units]
461            self.addCheckedListToModel(self._magnet_model, checked_list)
462
463        self.addHeadersToModel(self._magnet_model)
464
465    def addExtraShells(self):
466        """
467        Add a combobox for multiple shell display
468        """
469        param_name, param_length = self.getMultiplicity(self.model_parameters)
470
471        if param_length == 0:
472            return
473
474        # cell 1: variable name
475        item1 = QtGui.QStandardItem(param_name)
476
477        func = QtGui.QComboBox()
478        func.addItems([str(i+1) for i in xrange(param_length)])
479        func.currentIndexChanged.connect(self.modifyShellsInList)
480
481        # cell 2: combobox
482        item2 = QtGui.QStandardItem()
483        self._model_model.appendRow([item1, item2])
484
485        # Beautify the row:  span columns 2-4
486        shell_row = self._model_model.rowCount()
487        shell_index = self._model_model.index(shell_row-1, 1)
488        self.lstParams.setIndexWidget(shell_index, func)
489
490        self._last_model_row = self._model_model.rowCount()
491
492
493    def modifyShellsInList(self, index):
494        """
495        Add/remove additional multishell parameters
496        """
497        # Find row location of the combobox
498        last_row = self._last_model_row
499        remove_rows = self._model_model.rowCount() - last_row
500
501        if remove_rows > 1:
502            self._model_model.removeRows(last_row, remove_rows)
503
504        multishell_parameters = self.getIterParams(self.model_parameters)
505
506        for i in xrange(index):
507            for par in multishell_parameters:
508                param_name = self.replaceShellName(par.name, i+2)
509                #param_name = str(p.name) + str(i+2)
510                item1 = QtGui.QStandardItem(param_name)
511                item1.setCheckable(True)
512                # check for polydisp params
513                if par.polydisperse:
514                    poly_item = QtGui.QStandardItem("Polydispersity")
515                    item1_1 = QtGui.QStandardItem("Distribution")
516                    # Find param in volume_params
517                    for p in self.model_parameters.form_volume_parameters:
518                        if p.name == par.name:
519                            item1_2 = QtGui.QStandardItem(str(p.default))
520                            item1_3 = QtGui.QStandardItem(str(p.limits[0]))
521                            item1_4 = QtGui.QStandardItem(str(p.limits[1]))
522                            item1_5 = QtGui.QStandardItem(p.units)
523                            poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
524                            break
525                    item1.appendRow([poly_item])
526
527                item2 = QtGui.QStandardItem(str(par.default))
528                item3 = QtGui.QStandardItem(str(par.limits[0]))
529                item4 = QtGui.QStandardItem(str(par.limits[1]))
530                item5 = QtGui.QStandardItem(par.units)
531                self._model_model.appendRow([item1, item2, item3, item4, item5])
532
533    def togglePoly(self, isChecked):
534        """
535        Enable/disable the polydispersity tab
536        """
537        self.tabFitting.setTabEnabled(TAB_POLY, isChecked)
538
539    def toggleMagnetism(self, isChecked):
540        """
541        Enable/disable the magnetism tab
542        """
543        self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked)
544
545    def toggle2D(self, isChecked):
546        """
547        Enable/disable the controls dependent on 1D/2D data instance
548        """
549        self.chkMagnetism.setEnabled(isChecked)
550        self.is2D = isChecked
551
Note: See TracBrowser for help on using the repository browser.