source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @ 0268aed

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

Plotting residuals in fitting.
PlotHelper? updates.
Minor refactoring.

  • Property mode set to 100755
File size: 29.9 KB
Line 
1import sys
2import json
3import os
4import numpy
5from collections import defaultdict
6
7import logging
8import traceback
9from twisted.internet import threads
10
11from PyQt4 import QtGui
12from PyQt4 import QtCore
13
14from sasmodels import generate
15from sasmodels import modelinfo
16from sasmodels.sasview_model import load_standard_models
17
18from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
19from sas.sasgui.guiframe.dataFitting import Data1D
20from sas.sasgui.guiframe.dataFitting import Data2D
21import sas.qtgui.GuiUtils as GuiUtils
22from sas.sasgui.perspectives.fitting.model_thread import Calc1D
23from sas.sasgui.perspectives.fitting.model_thread import Calc2D
24
25from UI.FittingWidgetUI import Ui_FittingWidgetUI
26from sas.qtgui.Perspectives.Fitting.FittingLogic import FittingLogic
27from sas.qtgui.Perspectives.Fitting import FittingUtilities
28
29TAB_MAGNETISM = 4
30TAB_POLY = 3
31CATEGORY_DEFAULT = "Choose category..."
32CATEGORY_STRUCTURE = "Structure Factor"
33STRUCTURE_DEFAULT = "None"
34QMIN_DEFAULT = 0.0005
35QMAX_DEFAULT = 0.5
36NPTS_DEFAULT = 50
37
38class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI):
39    """
40    Main widget for selecting form and structure factor models
41    """
42    def __init__(self, parent=None, data=None, id=1):
43
44        super(FittingWidget, self).__init__()
45
46        # Necessary globals
47        self.parent = parent
48        # SasModel is loaded
49        self.model_is_loaded = False
50        # Data[12]D passed and set
51        self.data_is_loaded = False
52        # Current SasModel in view
53        self.kernel_module = None
54        # Current SasModel view dimension
55        self.is2D = False
56        # Current SasModel is multishell
57        self.model_has_shells = False
58        # Utility variable to enable unselectable option in category combobox
59        self._previous_category_index = 0
60        # Utility variable for multishell display
61        self._last_model_row = 0
62        # Dictionary of {model name: model class} for the current category
63        self.models = {}
64
65        # Which tab is this widget displayed in?
66        self.tab_id = id
67
68        # Which shell is being currently displayed?
69        self.current_shell_displayed = 0
70
71        # Range parameters
72        self.q_range_min = QMIN_DEFAULT
73        self.q_range_max = QMAX_DEFAULT
74        self.npts = NPTS_DEFAULT
75
76        # Main Data[12]D holder
77        self.logic = FittingLogic(data=data)
78
79        # Main GUI setup up
80        self.setupUi(self)
81        self.setWindowTitle("Fitting")
82        self.communicate = self.parent.communicate
83
84        # Define bold font for use in various controls
85        self.boldFont=QtGui.QFont()
86        self.boldFont.setBold(True)
87
88        # Set data label
89        self.label.setFont(self.boldFont)
90        self.label.setText("No data loaded")
91        self.lblFilename.setText("")
92
93        # Set the main models
94        # We can't use a single model here, due to restrictions on flattening
95        # the model tree with subclassed QAbstractProxyModel...
96        self._model_model = QtGui.QStandardItemModel()
97        self._poly_model = QtGui.QStandardItemModel()
98        self._magnet_model = QtGui.QStandardItemModel()
99
100        # Param model displayed in param list
101        self.lstParams.setModel(self._model_model)
102        self.readCategoryInfo()
103        self.model_parameters = None
104        self.lstParams.setAlternatingRowColors(True)
105
106        # Poly model displayed in poly list
107        self.lstPoly.setModel(self._poly_model)
108        self.setPolyModel()
109        self.setTableProperties(self.lstPoly)
110
111        # Magnetism model displayed in magnetism list
112        self.lstMagnetic.setModel(self._magnet_model)
113        self.setMagneticModel()
114        self.setTableProperties(self.lstMagnetic)
115
116        # Defaults for the structure factors
117        self.setDefaultStructureCombo()
118
119        # Make structure factor and model CBs disabled
120        self.disableModelCombo()
121        self.disableStructureCombo()
122
123        # Generate the category list for display
124        category_list = sorted(self.master_category_dict.keys())
125        self.cbCategory.addItem(CATEGORY_DEFAULT)
126        self.cbCategory.addItems(category_list)
127        self.cbCategory.addItem(CATEGORY_STRUCTURE)
128        self.cbCategory.setCurrentIndex(0)
129
130        self._index = None
131        if data is not None:
132            self.data = data
133
134        # Connect signals to controls
135        self.initializeSignals()
136
137        # Initial control state
138        self.initializeControls()
139
140    @property
141    def data(self):
142        return self.logic.data
143
144    @data.setter
145    def data(self, value):
146        """ data setter """
147        assert isinstance(value[0], QtGui.QStandardItem)
148        # _index contains the QIndex with data
149        self._index = value[0]
150
151        # Update logics with data items
152        self.logic.data = GuiUtils.dataFromItem(value[0])
153
154        self.data_is_loaded = True
155        # Tag along functionality
156        self.label.setText("Data loaded from: ")
157        self.lblFilename.setText(self.logic.data.filename)
158        self.updateQRange()
159        self.cmdFit.setEnabled(True)
160
161    def acceptsData(self):
162        """ Tells the caller this widget can accept new dataset """
163        return not self.data_is_loaded
164
165    def disableModelCombo(self):
166        """ Disable the combobox """
167        self.cbModel.setEnabled(False)
168        self.lblModel.setEnabled(False)
169
170    def enableModelCombo(self):
171        """ Enable the combobox """
172        self.cbModel.setEnabled(True)
173        self.lblModel.setEnabled(True)
174
175    def disableStructureCombo(self):
176        """ Disable the combobox """
177        self.cbStructureFactor.setEnabled(False)
178        self.lblStructure.setEnabled(False)
179
180    def enableStructureCombo(self):
181        """ Enable the combobox """
182        self.cbStructureFactor.setEnabled(True)
183        self.lblStructure.setEnabled(True)
184
185    def togglePoly(self, isChecked):
186        """
187        Enable/disable the polydispersity tab
188        """
189        self.tabFitting.setTabEnabled(TAB_POLY, isChecked)
190
191    def toggleMagnetism(self, isChecked):
192        """
193        Enable/disable the magnetism tab
194        """
195        self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked)
196
197    def toggle2D(self, isChecked):
198        """
199        Enable/disable the controls dependent on 1D/2D data instance
200        """
201        self.chkMagnetism.setEnabled(isChecked)
202        self.is2D = isChecked
203
204    def initializeControls(self):
205        """
206        Set initial control enablement
207        """
208        self.cmdFit.setEnabled(False)
209        self.cmdPlot.setEnabled(True)
210        self.chkPolydispersity.setEnabled(True)
211        self.chkPolydispersity.setCheckState(False)
212        self.chk2DView.setEnabled(True)
213        self.chk2DView.setCheckState(False)
214        self.chkMagnetism.setEnabled(False)
215        self.chkMagnetism.setCheckState(False)
216        # Tabs
217        self.tabFitting.setTabEnabled(TAB_POLY, False)
218        self.tabFitting.setTabEnabled(TAB_MAGNETISM, False)
219        self.lblChi2Value.setText("---")
220        # Update Q Ranges
221        self.updateQRange()
222
223    def initializeSignals(self):
224        """
225        Connect GUI element signals
226        """
227        # Comboboxes
228        self.cbStructureFactor.currentIndexChanged.connect(self.onSelectStructureFactor)
229        self.cbCategory.currentIndexChanged.connect(self.onSelectCategory)
230        self.cbModel.currentIndexChanged.connect(self.onSelectModel)
231        # Checkboxes
232        self.chk2DView.toggled.connect(self.toggle2D)
233        self.chkPolydispersity.toggled.connect(self.togglePoly)
234        self.chkMagnetism.toggled.connect(self.toggleMagnetism)
235        # Buttons
236        self.cmdFit.clicked.connect(self.onFit)
237        self.cmdPlot.clicked.connect(self.onPlot)
238        # Line edits
239        self.txtNpts.textChanged.connect(self.onNpts)
240        self.txtMinRange.textChanged.connect(self.onMinRange)
241        self.txtMaxRange.textChanged.connect(self.onMaxRange)
242
243        # Respond to change in parameters from the UI
244        self._model_model.itemChanged.connect(self.updateParamsFromModel)
245        self._poly_model.itemChanged.connect(self.onPolyModelChange)
246        # TODO after the poly_model prototype accepted
247        #self._magnet_model.itemChanged.connect(self.onMagneticModelChange)
248
249    def onSelectModel(self):
250        """
251        Respond to select Model from list event
252        """
253        model = str(self.cbModel.currentText())
254
255        # Reset structure factor
256        self.cbStructureFactor.setCurrentIndex(0)
257
258        # SasModel -> QModel
259        self.SASModelToQModel(model)
260
261        if self.data_is_loaded:
262            self.calculateQGridForModel()
263        else:
264            # Create default datasets if no data passed
265            self.createDefaultDataset()
266
267    def onSelectStructureFactor(self):
268        """
269        Select Structure Factor from list
270        """
271        model = str(self.cbModel.currentText())
272        category = str(self.cbCategory.currentText())
273        structure = str(self.cbStructureFactor.currentText())
274        if category == CATEGORY_STRUCTURE:
275            model = None
276        self.SASModelToQModel(model, structure_factor=structure)
277
278    def onSelectCategory(self):
279        """
280        Select Category from list
281        """
282        category = str(self.cbCategory.currentText())
283        # Check if the user chose "Choose category entry"
284        if category == CATEGORY_DEFAULT:
285            # if the previous category was not the default, keep it.
286            # Otherwise, just return
287            if self._previous_category_index != 0:
288                # We need to block signals, or else state changes on perceived unchanged conditions
289                self.cbCategory.blockSignals(True)
290                self.cbCategory.setCurrentIndex(self._previous_category_index)
291                self.cbCategory.blockSignals(False)
292            return
293
294        if category == CATEGORY_STRUCTURE:
295            self.disableModelCombo()
296            self.enableStructureCombo()
297            self._model_model.clear()
298            return
299
300        # Safely clear and enable the model combo
301        self.cbModel.blockSignals(True)
302        self.cbModel.clear()
303        self.cbModel.blockSignals(False)
304        self.enableModelCombo()
305        self.disableStructureCombo()
306
307        self._previous_category_index = self.cbCategory.currentIndex()
308        # Retrieve the list of models
309        model_list = self.master_category_dict[category]
310        models = []
311        # Populate the models combobox
312        self.cbModel.addItems(sorted([model for (model, _) in model_list]))
313
314    def onPolyModelChange(self, item):
315        """
316        Callback method for updating the main model and sasmodel
317        parameters with the GUI values in the polydispersity view
318        """
319        model_column = item.column()
320        model_row = item.row()
321        name_index = self._poly_model.index(model_row, 0)
322        # Extract changed value. Assumes proper validation by QValidator/Delegate
323        # Checkbox in column 0
324        if model_column == 0:
325            value = item.checkState()
326        else:
327            try:
328                value = float(item.text())
329            except ValueError:
330                # Can't be converted properly, bring back the old value and exit
331                return
332
333        parameter_name = str(self._poly_model.data(name_index).toPyObject()) # "distribution of sld" etc.
334        if "Distribution of" in parameter_name:
335            parameter_name = parameter_name[16:]
336        property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()) # Value, min, max, etc.
337        # print "%s(%s) => %d" % (parameter_name, property_name, value)
338
339        # Update the sasmodel
340        #self.kernel_module.params[parameter_name] = value
341
342        # Reload the main model - may not be required if no variable is shown in main view
343        #model = str(self.cbModel.currentText())
344        #self.SASModelToQModel(model)
345
346        pass # debug anchor
347
348    def onFit(self):
349        """
350        Perform fitting on the current data
351        """
352        # TODO: everything here
353        #self.calculate1DForModel()
354        #calc_fit = FitThread(handler=handler,
355        #                     fn=fitter_list,
356        #                     batch_inputs=batch_inputs,
357        #                     batch_outputs=batch_outputs,
358        #                     page_id=list_page_id,
359        #                     updatefn=handler.update_fit,
360        #                     completefn=self._fit_completed)
361
362        pass
363
364    def onPlot(self):
365        """
366        Plot the current set of data
367        """
368        if self.data is None :#or not self.data.is_data:
369            self.createDefaultDataset()
370        self.calculateQGridForModel()
371
372    def onNpts(self, text):
373        """
374        Callback for number of points line edit update
375        """
376        # assumes type/value correctness achieved with QValidator
377        try:
378            self.npts = int(text)
379        except ValueError:
380            # TODO
381            # This will return the old value to model/view and return
382            # notifying the user about format available.
383            pass
384
385    def onMinRange(self, text):
386        """
387        Callback for minimum range of points line edit update
388        """
389        # assumes type/value correctness achieved with QValidator
390        try:
391            self.q_range_min = float(text)
392        except ValueError:
393            # TODO
394            # This will return the old value to model/view and return
395            # notifying the user about format available.
396            return
397        # set Q range labels on the main tab
398        self.lblMinRangeDef.setText(str(self.q_range_min))
399
400    def onMaxRange(self, text):
401        """
402        Callback for maximum range of points line edit update
403        """
404        # assumes type/value correctness achieved with QValidator
405        try:
406            self.q_range_max = float(text)
407        except:
408            pass
409        # set Q range labels on the main tab
410        self.lblMaxRangeDef.setText(str(self.q_range_max))
411
412    def setDefaultStructureCombo(self):
413        """
414        Fill in the structure factors combo box with defaults
415        """
416        structure_factor_list = self.master_category_dict.pop(CATEGORY_STRUCTURE)
417        factors = [factor[0] for factor in structure_factor_list]
418        factors.insert(0, STRUCTURE_DEFAULT)
419        self.cbStructureFactor.clear()
420        self.cbStructureFactor.addItems(sorted(factors))
421
422    def createDefaultDataset(self):
423        """
424        Generate default Dataset 1D/2D for the given model
425        """
426        # Create default datasets if no data passed
427        if self.is2D:
428            qmax = self.q_range_max/numpy.sqrt(2)
429            qstep = self.npts
430            self.logic.createDefault2dData(qmax, qstep, self.tab_id)
431        else:
432            interval = numpy.linspace(start=self.q_range_min, stop=self.q_range_max,
433                        num=self.npts, endpoint=True)
434            self.logic.createDefault1dData(interval, self.tab_id)
435
436    def readCategoryInfo(self):
437        """
438        Reads the categories in from file
439        """
440        self.master_category_dict = defaultdict(list)
441        self.by_model_dict = defaultdict(list)
442        self.model_enabled_dict = defaultdict(bool)
443
444        categorization_file = CategoryInstaller.get_user_file()
445        if not os.path.isfile(categorization_file):
446            categorization_file = CategoryInstaller.get_default_file()
447        with open(categorization_file, 'rb') as cat_file:
448            self.master_category_dict = json.load(cat_file)
449            self.regenerateModelDict()
450
451        # Load the model dict
452        models = load_standard_models()
453        for model in models:
454            self.models[model.name] = model
455
456    def regenerateModelDict(self):
457        """
458        Regenerates self.by_model_dict which has each model name as the
459        key and the list of categories belonging to that model
460        along with the enabled mapping
461        """
462        self.by_model_dict = defaultdict(list)
463        for category in self.master_category_dict:
464            for (model, enabled) in self.master_category_dict[category]:
465                self.by_model_dict[model].append(category)
466                self.model_enabled_dict[model] = enabled
467
468    def addBackgroundToModel(self, model):
469        """
470        Adds background parameter with default values to the model
471        """
472        assert isinstance(model, QtGui.QStandardItemModel)
473        checked_list = ['background', '0.001', '-inf', 'inf', '1/cm']
474        FittingUtilities.addCheckedListToModel(model, checked_list)
475
476    def addScaleToModel(self, model):
477        """
478        Adds scale parameter with default values to the model
479        """
480        assert isinstance(model, QtGui.QStandardItemModel)
481        checked_list = ['scale', '1.0', '0.0', 'inf', '']
482        FittingUtilities.addCheckedListToModel(model, checked_list)
483
484    def updateQRange(self):
485        """
486        Updates Q Range display
487        """
488        if self.data_is_loaded:
489            self.q_range_min, self.q_range_max, self.npts = self.logic.computeDataRange()
490        # set Q range labels on the main tab
491        self.lblMinRangeDef.setText(str(self.q_range_min))
492        self.lblMaxRangeDef.setText(str(self.q_range_max))
493        # set Q range labels on the options tab
494        self.txtMaxRange.setText(str(self.q_range_max))
495        self.txtMinRange.setText(str(self.q_range_min))
496        self.txtNpts.setText(str(self.npts))
497
498    def SASModelToQModel(self, model_name, structure_factor=None):
499        """
500        Setting model parameters into table based on selected category
501        """
502        # TODO - modify for structure factor-only choice
503
504        # Crete/overwrite model items
505        self._model_model.clear()
506
507        kernel_module = generate.load_kernel_module(model_name)
508        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', []))
509
510        # Instantiate the current sasmodel
511        self.kernel_module = self.models[model_name]()
512
513        # Explicitly add scale and background with default values
514        self.addScaleToModel(self._model_model)
515        self.addBackgroundToModel(self._model_model)
516
517        # Update the QModel
518        FittingUtilities.addParametersToModel(self.model_parameters, self._model_model)
519        # Update the counter used for multishell display
520        self._last_model_row = self._model_model.rowCount()
521
522        FittingUtilities.addHeadersToModel(self._model_model)
523
524        # Add structure factor
525        if structure_factor is not None and structure_factor != "None":
526            structure_module = generate.load_kernel_module(structure_factor)
527            structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', []))
528            FittingUtilities.addSimpleParametersToModel(structure_parameters, self._model_model)
529            # Update the counter used for multishell display
530            self._last_model_row = self._model_model.rowCount()
531        else:
532            self.addStructureFactor()
533
534        # Multishell models need additional treatment
535        self.addExtraShells()
536
537        # Add polydispersity to the model
538        self.setPolyModel()
539        # Add magnetic parameters to the model
540        self.setMagneticModel()
541
542        # Adjust the table cells width
543        self.lstParams.resizeColumnToContents(0)
544        self.lstParams.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
545
546        # Now we claim the model has been loaded
547        self.model_is_loaded = True
548
549        # Update Q Ranges
550        self.updateQRange()
551
552    def updateParamsFromModel(self, item):
553        """
554        Callback method for updating the sasmodel parameters with the GUI values
555        """
556        model_column = item.column()
557        model_row = item.row()
558        name_index = self._model_model.index(model_row, 0)
559
560        if model_column == 0:
561            # Assure we're dealing with checkboxes
562            if not item.isCheckable():
563                return
564            status = item.checkState()
565            # If multiple rows selected - toggle all of them
566            rows = [s.row() for s in self.lstParams.selectionModel().selectedRows()]
567
568            # Switch off signaling from the model to avoid multiple calls
569            self._model_model.blockSignals(True)
570            # Convert to proper indices and set requested enablement
571            items = [self._model_model.item(row, 0).setCheckState(status) for row in rows]
572            self._model_model.blockSignals(False)
573            return
574
575        # Extract changed value. Assumes proper validation by QValidator/Delegate
576        value = float(item.text())
577        parameter_name = str(self._model_model.data(name_index).toPyObject()) # sld, background etc.
578        property_name = str(self._model_model.headerData(1, model_column).toPyObject()) # Value, min, max, etc.
579
580        # print "%s(%s) => %d" % (parameter_name, property_name, value)
581        self.kernel_module.params[parameter_name] = value
582
583        # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
584
585        # magnetic params in self.kernel_module.details['M0:parameter_name'] = value
586        # multishell params in self.kernel_module.details[??] = value
587
588    def nameForFittedData(self, name):
589        """
590        Generate name for the current fit
591        """
592        if self.is2D:
593            name += "2d"
594        name = "M%i [%s]" % (self.tab_id, name)
595        return name
596
597    def createNewIndex(self, fitted_data):
598        """
599        Create a model or theory index with passed Data1D/Data2D
600        """
601        if self.data_is_loaded:
602            if not fitted_data.name:
603                name = self.nameForFittedData(self.data.filename)
604                fitted_data.title = name
605                fitted_data.name = name
606                fitted_data.filename = name
607            self.updateModelIndex(fitted_data)
608        else:
609            name = self.nameForFittedData(self.kernel_module.name)
610            fitted_data.title = name
611            fitted_data.name = name
612            fitted_data.filename = name
613            fitted_data.symbol = "Line"
614            self.createTheoryIndex(fitted_data)
615
616    def updateModelIndex(self, fitted_data):
617        """
618        Update a QStandardModelIndex containing model data
619        """
620        if fitted_data.name is None:
621            name = self.nameForFittedData(self.logic.data.filename)
622            fitted_data.title = name
623            fitted_data.name = name
624        else:
625            name = fitted_data.name
626        # Make this a line if no other defined
627        if fitted_data.symbol is None:
628            fitted_data.symbol = 'Line'
629        # Notify the GUI manager so it can update the main model in DataExplorer
630        GuiUtils.updateModelItemWithPlot(self._index, QtCore.QVariant(fitted_data), name)
631
632    def createTheoryIndex(self, fitted_data):
633        """
634        Create a QStandardModelIndex containing model data
635        """
636        if fitted_data.name is None:
637            name = self.nameForFittedData(self.kernel_module.name)
638            fitted_data.title = name
639            fitted_data.name = name
640            fitted_data.filename = name
641        else:
642            name = fitted_data.name
643        # Notify the GUI manager so it can create the theory model in DataExplorer
644        new_item = GuiUtils.createModelItemWithPlot(QtCore.QVariant(fitted_data), name=name)
645        self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item)
646
647    def methodCalculateForData(self):
648        '''return the method for data calculation'''
649        return Calc1D if isinstance(self.data, Data1D) else Calc2D
650
651    def methodCompleteForData(self):
652        '''return the method for result parsin on calc complete '''
653        return self.complete1D if isinstance(self.data, Data1D) else self.complete2D
654
655    def calculateQGridForModel(self):
656        """
657        Prepare the fitting data object, based on current ModelModel
658        """
659        # Awful API to a backend method.
660        method = self.methodCalculateForData()(data=self.data,
661                              model=self.kernel_module,
662                              page_id=0,
663                              qmin=self.q_range_min,
664                              qmax=self.q_range_max,
665                              smearer=None,
666                              state=None,
667                              weight=None,
668                              fid=None,
669                              toggle_mode_on=False,
670                              completefn=None,
671                              update_chisqr=True,
672                              exception_handler=self.calcException,
673                              source=None)
674
675        calc_thread = threads.deferToThread(method.compute)
676        calc_thread.addCallback(self.methodCompleteForData())
677
678    def complete1D(self, return_data):
679        """
680        Plot the current 1D data
681        """
682        fitted_plot = self.logic.new1DPlot(return_data)
683        self.calculateResiduals(fitted_plot)
684
685    def complete2D(self, return_data):
686        """
687        Plot the current 2D data
688        """
689        fitted_data = self.logic.new2DPlot(return_data)
690        self.calculateResiduals(fitted_data)
691
692    def calculateResiduals(self, fitted_data):
693        """
694        Calculate and print Chi2 and display chart of residuals
695        """
696        # Create a new index for holding data
697        self.createNewIndex(fitted_data)
698        # Calculate difference between return_data and logic.data
699        chi2 = FittingUtilities.calculateChi2(fitted_data, self.logic.data)
700        # Update the control
701        self.lblChi2Value.setText(GuiUtils.formatNumber(chi2, high=True))
702
703        # Plot residuals if actual data
704        if self.data_is_loaded:
705            residuals_plot = FittingUtilities.plotResiduals(self.data, fitted_data)
706            self.createNewIndex(residuals_plot)
707
708    def calcException(self, etype, value, tb):
709        """
710        Something horrible happened in the deferred.
711        """
712        logging.error("".join(traceback.format_exception(etype, value, tb)))
713
714    def setTableProperties(self, table):
715        """
716        Setting table properties
717        """
718        # Table properties
719        table.verticalHeader().setVisible(False)
720        table.setAlternatingRowColors(True)
721        table.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
722        table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
723        table.resizeColumnsToContents()
724
725        # Header
726        header = table.horizontalHeader()
727        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
728
729        header.ResizeMode(QtGui.QHeaderView.Interactive)
730        # Resize column 0 and 6 to content
731        header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
732        header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents)
733
734    def setPolyModel(self):
735        """
736        Set polydispersity values
737        """
738        if not self.model_parameters:
739            return
740        self._poly_model.clear()
741        for row, param in enumerate(self.model_parameters.form_volume_parameters):
742            # Counters should not be included
743            if not param.polydisperse:
744                continue
745
746            # Potential multishell params
747            checked_list = ["Distribution of "+param.name, str(param.default),
748                            str(param.limits[0]), str(param.limits[1]),
749                            "35", "3", ""]
750            FittingUtilities.addCheckedListToModel(self._poly_model, checked_list)
751
752            #TODO: Need to find cleaner way to input functions
753            func = QtGui.QComboBox()
754            func.addItems(['rectangle', 'array', 'lognormal', 'gaussian', 'schulz',])
755            func_index = self.lstPoly.model().index(row, 6)
756            self.lstPoly.setIndexWidget(func_index, func)
757
758        FittingUtilities.addPolyHeadersToModel(self._poly_model)
759
760    def setMagneticModel(self):
761        """
762        Set magnetism values on model
763        """
764        if not self.model_parameters:
765            return
766        self._magnet_model.clear()
767        for param in self.model_parameters.call_parameters:
768            if param.type != "magnetic":
769                continue
770            checked_list = [param.name,
771                            str(param.default),
772                            str(param.limits[0]),
773                            str(param.limits[1]),
774                            param.units]
775            FittingUtilities.addCheckedListToModel(self._magnet_model, checked_list)
776
777        FittingUtilities.addHeadersToModel(self._magnet_model)
778
779    def addStructureFactor(self):
780        """
781        Add structure factors to the list of parameters
782        """
783        if self.kernel_module.is_form_factor:
784            self.enableStructureCombo()
785        else:
786            self.disableStructureCombo()
787
788    def addExtraShells(self):
789        """
790        Add a combobox for multiple shell display
791        """
792        param_name, param_length = FittingUtilities.getMultiplicity(self.model_parameters)
793
794        if param_length == 0:
795            return
796
797        # cell 1: variable name
798        item1 = QtGui.QStandardItem(param_name)
799
800        func = QtGui.QComboBox()
801        # Available range of shells displayed in the combobox
802        func.addItems([str(i) for i in xrange(param_length+1)])
803
804        # Respond to index change
805        func.currentIndexChanged.connect(self.modifyShellsInList)
806
807        # cell 2: combobox
808        item2 = QtGui.QStandardItem()
809        self._model_model.appendRow([item1, item2])
810
811        # Beautify the row:  span columns 2-4
812        shell_row = self._model_model.rowCount()
813        shell_index = self._model_model.index(shell_row-1, 1)
814
815        self.lstParams.setIndexWidget(shell_index, func)
816        self._last_model_row = self._model_model.rowCount()
817
818        # Set the index to the state-kept value
819        func.setCurrentIndex(self.current_shell_displayed
820                             if self.current_shell_displayed < func.count() else 0)
821
822    def modifyShellsInList(self, index):
823        """
824        Add/remove additional multishell parameters
825        """
826        # Find row location of the combobox
827        last_row = self._last_model_row
828        remove_rows = self._model_model.rowCount() - last_row
829
830        if remove_rows > 1:
831            self._model_model.removeRows(last_row, remove_rows)
832
833        FittingUtilities.addShellsToModel(self.model_parameters, self._model_model, index)
834        self.current_shell_displayed = index
835
Note: See TracBrowser for help on using the repository browser.