source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @ 9f25bce

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

Towards more 1D plots responding to data change.
Minor bug fixes.

  • Property mode set to 100755
File size: 30.6 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.editingFinished.connect(self.onNpts)
240        self.txtMinRange.editingFinished.connect(self.onMinRange)
241        self.txtMaxRange.editingFinished.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 :
369            self.createDefaultDataset()
370        self.calculateQGridForModel()
371
372    #def onNpts(self, text):
373    def onNpts(self):
374        """
375        Callback for number of points line edit update
376        """
377        # assumes type/value correctness achieved with QValidator
378        try:
379            self.npts = int(self.txtNpts.text())
380        except ValueError:
381            # TODO
382            # This will return the old value to model/view and return
383            # notifying the user about format available.
384            pass
385        # Force redisplay
386        if self.model_is_loaded:
387            self.onPlot()
388
389    #def onMinRange(self, text):
390    def onMinRange(self):
391        """
392        Callback for minimum range of points line edit update
393        """
394        # assumes type/value correctness achieved with QValidator
395        try:
396            self.q_range_min = float(self.txtMinRange.text())
397        except ValueError:
398            # TODO
399            # This will return the old value to model/view and return
400            # notifying the user about format available.
401            return
402        # set Q range labels on the main tab
403        self.lblMinRangeDef.setText(str(self.q_range_min))
404        if self.model_is_loaded:
405            self.onPlot()
406
407    #def onMaxRange(self, text):
408    def onMaxRange(self):
409        """
410        Callback for maximum range of points line edit update
411        """
412        # assumes type/value correctness achieved with QValidator
413        try:
414            self.q_range_max = float(self.txtMaxRange.text())
415        except:
416            pass
417        # set Q range labels on the main tab
418        self.lblMaxRangeDef.setText(str(self.q_range_max))
419        if self.model_is_loaded:
420            self.onPlot()
421
422    def setDefaultStructureCombo(self):
423        """
424        Fill in the structure factors combo box with defaults
425        """
426        structure_factor_list = self.master_category_dict.pop(CATEGORY_STRUCTURE)
427        factors = [factor[0] for factor in structure_factor_list]
428        factors.insert(0, STRUCTURE_DEFAULT)
429        self.cbStructureFactor.clear()
430        self.cbStructureFactor.addItems(sorted(factors))
431
432    def createDefaultDataset(self):
433        """
434        Generate default Dataset 1D/2D for the given model
435        """
436        # Create default datasets if no data passed
437        if self.is2D:
438            qmax = self.q_range_max/numpy.sqrt(2)
439            qstep = self.npts
440            self.logic.createDefault2dData(qmax, qstep, self.tab_id)
441        else:
442            interval = numpy.linspace(start=self.q_range_min, stop=self.q_range_max,
443                        num=self.npts, endpoint=True)
444            self.logic.createDefault1dData(interval, self.tab_id)
445
446    def readCategoryInfo(self):
447        """
448        Reads the categories in from file
449        """
450        self.master_category_dict = defaultdict(list)
451        self.by_model_dict = defaultdict(list)
452        self.model_enabled_dict = defaultdict(bool)
453
454        categorization_file = CategoryInstaller.get_user_file()
455        if not os.path.isfile(categorization_file):
456            categorization_file = CategoryInstaller.get_default_file()
457        with open(categorization_file, 'rb') as cat_file:
458            self.master_category_dict = json.load(cat_file)
459            self.regenerateModelDict()
460
461        # Load the model dict
462        models = load_standard_models()
463        for model in models:
464            self.models[model.name] = model
465
466    def regenerateModelDict(self):
467        """
468        Regenerates self.by_model_dict which has each model name as the
469        key and the list of categories belonging to that model
470        along with the enabled mapping
471        """
472        self.by_model_dict = defaultdict(list)
473        for category in self.master_category_dict:
474            for (model, enabled) in self.master_category_dict[category]:
475                self.by_model_dict[model].append(category)
476                self.model_enabled_dict[model] = enabled
477
478    def addBackgroundToModel(self, model):
479        """
480        Adds background parameter with default values to the model
481        """
482        assert isinstance(model, QtGui.QStandardItemModel)
483        checked_list = ['background', '0.001', '-inf', 'inf', '1/cm']
484        FittingUtilities.addCheckedListToModel(model, checked_list)
485
486    def addScaleToModel(self, model):
487        """
488        Adds scale parameter with default values to the model
489        """
490        assert isinstance(model, QtGui.QStandardItemModel)
491        checked_list = ['scale', '1.0', '0.0', 'inf', '']
492        FittingUtilities.addCheckedListToModel(model, checked_list)
493
494    def updateQRange(self):
495        """
496        Updates Q Range display
497        """
498        if self.data_is_loaded:
499            self.q_range_min, self.q_range_max, self.npts = self.logic.computeDataRange()
500        # set Q range labels on the main tab
501        self.lblMinRangeDef.setText(str(self.q_range_min))
502        self.lblMaxRangeDef.setText(str(self.q_range_max))
503        # set Q range labels on the options tab
504        self.txtMaxRange.setText(str(self.q_range_max))
505        self.txtMinRange.setText(str(self.q_range_min))
506        self.txtNpts.setText(str(self.npts))
507
508    def SASModelToQModel(self, model_name, structure_factor=None):
509        """
510        Setting model parameters into table based on selected category
511        """
512        # TODO - modify for structure factor-only choice
513
514        # Crete/overwrite model items
515        self._model_model.clear()
516
517        kernel_module = generate.load_kernel_module(model_name)
518        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', []))
519
520        # Instantiate the current sasmodel
521        self.kernel_module = self.models[model_name]()
522
523        # Explicitly add scale and background with default values
524        self.addScaleToModel(self._model_model)
525        self.addBackgroundToModel(self._model_model)
526
527        # Update the QModel
528        FittingUtilities.addParametersToModel(self.model_parameters, self._model_model)
529        # Update the counter used for multishell display
530        self._last_model_row = self._model_model.rowCount()
531
532        FittingUtilities.addHeadersToModel(self._model_model)
533
534        # Add structure factor
535        if structure_factor is not None and structure_factor != "None":
536            structure_module = generate.load_kernel_module(structure_factor)
537            structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', []))
538            FittingUtilities.addSimpleParametersToModel(structure_parameters, self._model_model)
539            # Update the counter used for multishell display
540            self._last_model_row = self._model_model.rowCount()
541        else:
542            self.addStructureFactor()
543
544        # Multishell models need additional treatment
545        self.addExtraShells()
546
547        # Add polydispersity to the model
548        self.setPolyModel()
549        # Add magnetic parameters to the model
550        self.setMagneticModel()
551
552        # Adjust the table cells width
553        self.lstParams.resizeColumnToContents(0)
554        self.lstParams.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
555
556        # Now we claim the model has been loaded
557        self.model_is_loaded = True
558
559        # Update Q Ranges
560        self.updateQRange()
561
562    def updateParamsFromModel(self, item):
563        """
564        Callback method for updating the sasmodel parameters with the GUI values
565        """
566        model_column = item.column()
567        model_row = item.row()
568        name_index = self._model_model.index(model_row, 0)
569
570        if model_column == 0:
571            # Assure we're dealing with checkboxes
572            if not item.isCheckable():
573                return
574            status = item.checkState()
575            # If multiple rows selected - toggle all of them
576            rows = [s.row() for s in self.lstParams.selectionModel().selectedRows()]
577
578            # Switch off signaling from the model to avoid multiple calls
579            self._model_model.blockSignals(True)
580            # Convert to proper indices and set requested enablement
581            items = [self._model_model.item(row, 0).setCheckState(status) for row in rows]
582            self._model_model.blockSignals(False)
583            return
584
585        # Extract changed value. Assumes proper validation by QValidator/Delegate
586        value = float(item.text())
587        parameter_name = str(self._model_model.data(name_index).toPyObject()) # sld, background etc.
588        property_name = str(self._model_model.headerData(1, model_column).toPyObject()) # Value, min, max, etc.
589
590        # print "%s(%s) => %d" % (parameter_name, property_name, value)
591        self.kernel_module.params[parameter_name] = value
592
593        # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
594
595        # magnetic params in self.kernel_module.details['M0:parameter_name'] = value
596        # multishell params in self.kernel_module.details[??] = value
597
598        # Force the chart update
599        self.onPlot()
600
601    def nameForFittedData(self, name):
602        """
603        Generate name for the current fit
604        """
605        if self.is2D:
606            name += "2d"
607        name = "M%i [%s]" % (self.tab_id, name)
608        return name
609
610    def createNewIndex(self, fitted_data):
611        """
612        Create a model or theory index with passed Data1D/Data2D
613        """
614        if self.data_is_loaded:
615            if not fitted_data.name:
616                name = self.nameForFittedData(self.data.filename)
617                fitted_data.title = name
618                fitted_data.name = name
619                fitted_data.filename = name
620                fitted_data.symbol = "Line"
621            self.updateModelIndex(fitted_data)
622        else:
623            name = self.nameForFittedData(self.kernel_module.name)
624            fitted_data.title = name
625            fitted_data.name = name
626            fitted_data.filename = name
627            fitted_data.symbol = "Line"
628            self.createTheoryIndex(fitted_data)
629
630    def updateModelIndex(self, fitted_data):
631        """
632        Update a QStandardModelIndex containing model data
633        """
634        if fitted_data.name is None:
635            name = self.nameForFittedData(self.logic.data.filename)
636            fitted_data.title = name
637            fitted_data.name = name
638        else:
639            name = fitted_data.name
640        # Make this a line if no other defined
641        if hasattr(fitted_data, 'symbol') and fitted_data.symbol is None:
642            fitted_data.symbol = 'Line'
643        # Notify the GUI manager so it can update the main model in DataExplorer
644        GuiUtils.updateModelItemWithPlot(self._index, QtCore.QVariant(fitted_data), name)
645
646    def createTheoryIndex(self, fitted_data):
647        """
648        Create a QStandardModelIndex containing model data
649        """
650        if fitted_data.name is None:
651            name = self.nameForFittedData(self.kernel_module.name)
652            fitted_data.title = name
653            fitted_data.name = name
654            fitted_data.filename = name
655        else:
656            name = fitted_data.name
657        # Notify the GUI manager so it can create the theory model in DataExplorer
658        new_item = GuiUtils.createModelItemWithPlot(QtCore.QVariant(fitted_data), name=name)
659        self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item)
660
661    def methodCalculateForData(self):
662        '''return the method for data calculation'''
663        return Calc1D if isinstance(self.data, Data1D) else Calc2D
664
665    def methodCompleteForData(self):
666        '''return the method for result parsin on calc complete '''
667        return self.complete1D if isinstance(self.data, Data1D) else self.complete2D
668
669    def calculateQGridForModel(self):
670        """
671        Prepare the fitting data object, based on current ModelModel
672        """
673        # Awful API to a backend method.
674        method = self.methodCalculateForData()(data=self.data,
675                              model=self.kernel_module,
676                              page_id=0,
677                              qmin=self.q_range_min,
678                              qmax=self.q_range_max,
679                              smearer=None,
680                              state=None,
681                              weight=None,
682                              fid=None,
683                              toggle_mode_on=False,
684                              completefn=None,
685                              update_chisqr=True,
686                              exception_handler=self.calcException,
687                              source=None)
688
689        calc_thread = threads.deferToThread(method.compute)
690        calc_thread.addCallback(self.methodCompleteForData())
691
692    def complete1D(self, return_data):
693        """
694        Plot the current 1D data
695        """
696        fitted_plot = self.logic.new1DPlot(return_data, self.tab_id)
697        self.calculateResiduals(fitted_plot)
698
699    def complete2D(self, return_data):
700        """
701        Plot the current 2D data
702        """
703        fitted_data = self.logic.new2DPlot(return_data)
704        self.calculateResiduals(fitted_data)
705
706    def calculateResiduals(self, fitted_data):
707        """
708        Calculate and print Chi2 and display chart of residuals
709        """
710        # Create a new index for holding data
711        fitted_data.symbol = "Line"
712        self.createNewIndex(fitted_data)
713        # Calculate difference between return_data and logic.data
714        chi2 = FittingUtilities.calculateChi2(fitted_data, self.logic.data)
715        # Update the control
716        self.lblChi2Value.setText(GuiUtils.formatNumber(chi2, high=True))
717
718        # Plot residuals if actual data
719        if self.data_is_loaded:
720            residuals_plot = FittingUtilities.plotResiduals(self.data, fitted_data)
721            residuals_plot.id = "Residual " + residuals_plot.id
722            self.createNewIndex(residuals_plot)
723            self.communicate.plotUpdateSignal.emit([residuals_plot])
724
725        self.communicate.plotUpdateSignal.emit([fitted_data])
726
727    def calcException(self, etype, value, tb):
728        """
729        Something horrible happened in the deferred.
730        """
731        logging.error("".join(traceback.format_exception(etype, value, tb)))
732
733    def setTableProperties(self, table):
734        """
735        Setting table properties
736        """
737        # Table properties
738        table.verticalHeader().setVisible(False)
739        table.setAlternatingRowColors(True)
740        table.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
741        table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
742        table.resizeColumnsToContents()
743
744        # Header
745        header = table.horizontalHeader()
746        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
747
748        header.ResizeMode(QtGui.QHeaderView.Interactive)
749        # Resize column 0 and 6 to content
750        header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
751        header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents)
752
753    def setPolyModel(self):
754        """
755        Set polydispersity values
756        """
757        if not self.model_parameters:
758            return
759        self._poly_model.clear()
760        for row, param in enumerate(self.model_parameters.form_volume_parameters):
761            # Counters should not be included
762            if not param.polydisperse:
763                continue
764
765            # Potential multishell params
766            checked_list = ["Distribution of "+param.name, str(param.default),
767                            str(param.limits[0]), str(param.limits[1]),
768                            "35", "3", ""]
769            FittingUtilities.addCheckedListToModel(self._poly_model, checked_list)
770
771            #TODO: Need to find cleaner way to input functions
772            func = QtGui.QComboBox()
773            func.addItems(['rectangle', 'array', 'lognormal', 'gaussian', 'schulz',])
774            func_index = self.lstPoly.model().index(row, 6)
775            self.lstPoly.setIndexWidget(func_index, func)
776
777        FittingUtilities.addPolyHeadersToModel(self._poly_model)
778
779    def setMagneticModel(self):
780        """
781        Set magnetism values on model
782        """
783        if not self.model_parameters:
784            return
785        self._magnet_model.clear()
786        for param in self.model_parameters.call_parameters:
787            if param.type != "magnetic":
788                continue
789            checked_list = [param.name,
790                            str(param.default),
791                            str(param.limits[0]),
792                            str(param.limits[1]),
793                            param.units]
794            FittingUtilities.addCheckedListToModel(self._magnet_model, checked_list)
795
796        FittingUtilities.addHeadersToModel(self._magnet_model)
797
798    def addStructureFactor(self):
799        """
800        Add structure factors to the list of parameters
801        """
802        if self.kernel_module.is_form_factor:
803            self.enableStructureCombo()
804        else:
805            self.disableStructureCombo()
806
807    def addExtraShells(self):
808        """
809        Add a combobox for multiple shell display
810        """
811        param_name, param_length = FittingUtilities.getMultiplicity(self.model_parameters)
812
813        if param_length == 0:
814            return
815
816        # cell 1: variable name
817        item1 = QtGui.QStandardItem(param_name)
818
819        func = QtGui.QComboBox()
820        # Available range of shells displayed in the combobox
821        func.addItems([str(i) for i in xrange(param_length+1)])
822
823        # Respond to index change
824        func.currentIndexChanged.connect(self.modifyShellsInList)
825
826        # cell 2: combobox
827        item2 = QtGui.QStandardItem()
828        self._model_model.appendRow([item1, item2])
829
830        # Beautify the row:  span columns 2-4
831        shell_row = self._model_model.rowCount()
832        shell_index = self._model_model.index(shell_row-1, 1)
833
834        self.lstParams.setIndexWidget(shell_index, func)
835        self._last_model_row = self._model_model.rowCount()
836
837        # Set the index to the state-kept value
838        func.setCurrentIndex(self.current_shell_displayed
839                             if self.current_shell_displayed < func.count() else 0)
840
841    def modifyShellsInList(self, index):
842        """
843        Add/remove additional multishell parameters
844        """
845        # Find row location of the combobox
846        last_row = self._last_model_row
847        remove_rows = self._model_model.rowCount() - last_row
848
849        if remove_rows > 1:
850            self._model_model.removeRows(last_row, remove_rows)
851
852        FittingUtilities.addShellsToModel(self.model_parameters, self._model_model, index)
853        self.current_shell_displayed = index
854
Note: See TracBrowser for help on using the repository browser.