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
RevLine 
[60af928]1import sys
2import json
[cd31251]3import os
[5236449]4import numpy
[60af928]5from collections import defaultdict
6
[5236449]7import logging
8import traceback
[cbcdd2c]9from twisted.internet import threads
[5236449]10
[60af928]11from PyQt4 import QtGui
12from PyQt4 import QtCore
13
14from sasmodels import generate
15from sasmodels import modelinfo
[5236449]16from sasmodels.sasview_model import load_standard_models
17
[60af928]18from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
[5236449]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
[cbcdd2c]23from sas.sasgui.perspectives.fitting.model_thread import Calc2D
[5236449]24
25from UI.FittingWidgetUI import Ui_FittingWidgetUI
[4d457df]26from sas.qtgui.Perspectives.Fitting.FittingLogic import FittingLogic
27from sas.qtgui.Perspectives.Fitting import FittingUtilities
[60af928]28
29TAB_MAGNETISM = 4
30TAB_POLY = 3
[cbcdd2c]31CATEGORY_DEFAULT = "Choose category..."
[4d457df]32CATEGORY_STRUCTURE = "Structure Factor"
[351b53e]33STRUCTURE_DEFAULT = "None"
[cbcdd2c]34QMIN_DEFAULT = 0.0005
35QMAX_DEFAULT = 0.5
36NPTS_DEFAULT = 50
[60af928]37
38class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI):
39    """
[f46f6dc]40    Main widget for selecting form and structure factor models
[60af928]41    """
[811bec1]42    def __init__(self, parent=None, data=None, id=1):
[60af928]43
44        super(FittingWidget, self).__init__()
45
[86f88d1]46        # Necessary globals
[cbcdd2c]47        self.parent = parent
48        # SasModel is loaded
[60af928]49        self.model_is_loaded = False
[cbcdd2c]50        # Data[12]D passed and set
[5236449]51        self.data_is_loaded = False
[cbcdd2c]52        # Current SasModel in view
[5236449]53        self.kernel_module = None
[cbcdd2c]54        # Current SasModel view dimension
[60af928]55        self.is2D = False
[cbcdd2c]56        # Current SasModel is multishell
[86f88d1]57        self.model_has_shells = False
[cbcdd2c]58        # Utility variable to enable unselectable option in category combobox
[86f88d1]59        self._previous_category_index = 0
[cbcdd2c]60        # Utility variable for multishell display
[86f88d1]61        self._last_model_row = 0
[cbcdd2c]62        # Dictionary of {model name: model class} for the current category
[5236449]63        self.models = {}
64
65        # Which tab is this widget displayed in?
66        self.tab_id = id
67
[a9b568c]68        # Which shell is being currently displayed?
69        self.current_shell_displayed = 0
70
[811bec1]71        # Range parameters
[cbcdd2c]72        self.q_range_min = QMIN_DEFAULT
73        self.q_range_max = QMAX_DEFAULT
74        self.npts = NPTS_DEFAULT
[811bec1]75
[cbcdd2c]76        # Main Data[12]D holder
[4d457df]77        self.logic = FittingLogic(data=data)
[60af928]78
[86f88d1]79        # Main GUI setup up
[60af928]80        self.setupUi(self)
81        self.setWindowTitle("Fitting")
[cbcdd2c]82        self.communicate = self.parent.communicate
[60af928]83
[b1e36a3]84        # Define bold font for use in various controls
[a0f5c36]85        self.boldFont=QtGui.QFont()
86        self.boldFont.setBold(True)
87
88        # Set data label
[b1e36a3]89        self.label.setFont(self.boldFont)
90        self.label.setText("No data loaded")
91        self.lblFilename.setText("")
92
[86f88d1]93        # Set the main models
[cd31251]94        # We can't use a single model here, due to restrictions on flattening
95        # the model tree with subclassed QAbstractProxyModel...
[60af928]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)
[5236449]102        self.readCategoryInfo()
[60af928]103        self.model_parameters = None
[86f88d1]104        self.lstParams.setAlternatingRowColors(True)
[60af928]105
106        # Poly model displayed in poly list
[811bec1]107        self.lstPoly.setModel(self._poly_model)
[60af928]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
[5236449]116        # Defaults for the structure factors
[6f7f652]117        self.setDefaultStructureCombo()
118
[5236449]119        # Make structure factor and model CBs disabled
[6f7f652]120        self.disableModelCombo()
121        self.disableStructureCombo()
[60af928]122
[6f7f652]123        # Generate the category list for display
[60af928]124        category_list = sorted(self.master_category_dict.keys())
[86f88d1]125        self.cbCategory.addItem(CATEGORY_DEFAULT)
[60af928]126        self.cbCategory.addItems(category_list)
[4d457df]127        self.cbCategory.addItem(CATEGORY_STRUCTURE)
[6f7f652]128        self.cbCategory.setCurrentIndex(0)
[60af928]129
[b1e36a3]130        self._index = None
[5236449]131        if data is not None:
132            self.data = data
133
[86f88d1]134        # Connect signals to controls
135        self.initializeSignals()
[60af928]136
[86f88d1]137        # Initial control state
138        self.initializeControls()
[60af928]139
140    @property
141    def data(self):
[4d457df]142        return self.logic.data
[60af928]143
144    @data.setter
145    def data(self, value):
146        """ data setter """
[4d457df]147        assert isinstance(value[0], QtGui.QStandardItem)
[cbcdd2c]148        # _index contains the QIndex with data
[6fd4e36]149        self._index = value[0]
[b1e36a3]150
[4d457df]151        # Update logics with data items
152        self.logic.data = GuiUtils.dataFromItem(value[0])
153
[5236449]154        self.data_is_loaded = True
[cbcdd2c]155        # Tag along functionality
[b1e36a3]156        self.label.setText("Data loaded from: ")
[a0f5c36]157        self.lblFilename.setText(self.logic.data.filename)
[5236449]158        self.updateQRange()
159        self.cmdFit.setEnabled(True)
[60af928]160
[f46f6dc]161    def acceptsData(self):
162        """ Tells the caller this widget can accept new dataset """
[5236449]163        return not self.data_is_loaded
[f46f6dc]164
[6f7f652]165    def disableModelCombo(self):
[cbcdd2c]166        """ Disable the combobox """
[6f7f652]167        self.cbModel.setEnabled(False)
[b1e36a3]168        self.lblModel.setEnabled(False)
[6f7f652]169
170    def enableModelCombo(self):
[cbcdd2c]171        """ Enable the combobox """
[6f7f652]172        self.cbModel.setEnabled(True)
[b1e36a3]173        self.lblModel.setEnabled(True)
[6f7f652]174
175    def disableStructureCombo(self):
[cbcdd2c]176        """ Disable the combobox """
[6f7f652]177        self.cbStructureFactor.setEnabled(False)
[b1e36a3]178        self.lblStructure.setEnabled(False)
[6f7f652]179
180    def enableStructureCombo(self):
[cbcdd2c]181        """ Enable the combobox """
[6f7f652]182        self.cbStructureFactor.setEnabled(True)
[b1e36a3]183        self.lblStructure.setEnabled(True)
[6f7f652]184
[0268aed]185    def togglePoly(self, isChecked):
[5236449]186        """
[0268aed]187        Enable/disable the polydispersity tab
[5236449]188        """
[0268aed]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
[5236449]203
[86f88d1]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)
[cbcdd2c]216        # Tabs
[86f88d1]217        self.tabFitting.setTabEnabled(TAB_POLY, False)
218        self.tabFitting.setTabEnabled(TAB_MAGNETISM, False)
219        self.lblChi2Value.setText("---")
[5236449]220        # Update Q Ranges
221        self.updateQRange()
[86f88d1]222
223    def initializeSignals(self):
224        """
225        Connect GUI element signals
226        """
[cbcdd2c]227        # Comboboxes
[cd31251]228        self.cbStructureFactor.currentIndexChanged.connect(self.onSelectStructureFactor)
229        self.cbCategory.currentIndexChanged.connect(self.onSelectCategory)
230        self.cbModel.currentIndexChanged.connect(self.onSelectModel)
[cbcdd2c]231        # Checkboxes
[86f88d1]232        self.chk2DView.toggled.connect(self.toggle2D)
233        self.chkPolydispersity.toggled.connect(self.togglePoly)
234        self.chkMagnetism.toggled.connect(self.toggleMagnetism)
[cbcdd2c]235        # Buttons
[5236449]236        self.cmdFit.clicked.connect(self.onFit)
[cbcdd2c]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)
[cd31251]245        self._poly_model.itemChanged.connect(self.onPolyModelChange)
246        # TODO after the poly_model prototype accepted
247        #self._magnet_model.itemChanged.connect(self.onMagneticModelChange)
[86f88d1]248
[0268aed]249    def onSelectModel(self):
[cbcdd2c]250        """
[0268aed]251        Respond to select Model from list event
[cbcdd2c]252        """
[0268aed]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)
[6f7f652]277
[cd31251]278    def onSelectCategory(self):
[60af928]279        """
280        Select Category from list
281        """
[4d457df]282        category = str(self.cbCategory.currentText())
[86f88d1]283        # Check if the user chose "Choose category entry"
[4d457df]284        if category == CATEGORY_DEFAULT:
[86f88d1]285            # if the previous category was not the default, keep it.
286            # Otherwise, just return
287            if self._previous_category_index != 0:
[351b53e]288                # We need to block signals, or else state changes on perceived unchanged conditions
289                self.cbCategory.blockSignals(True)
[86f88d1]290                self.cbCategory.setCurrentIndex(self._previous_category_index)
[351b53e]291                self.cbCategory.blockSignals(False)
[86f88d1]292            return
293
[4d457df]294        if category == CATEGORY_STRUCTURE:
[6f7f652]295            self.disableModelCombo()
296            self.enableStructureCombo()
[29eb947]297            self._model_model.clear()
[6f7f652]298            return
299
[cbcdd2c]300        # Safely clear and enable the model combo
[6f7f652]301        self.cbModel.blockSignals(True)
302        self.cbModel.clear()
303        self.cbModel.blockSignals(False)
304        self.enableModelCombo()
305        self.disableStructureCombo()
306
[86f88d1]307        self._previous_category_index = self.cbCategory.currentIndex()
[cbcdd2c]308        # Retrieve the list of models
[4d457df]309        model_list = self.master_category_dict[category]
[6f7f652]310        models = []
[cbcdd2c]311        # Populate the models combobox
[b1e36a3]312        self.cbModel.addItems(sorted([model for (model, _) in model_list]))
[4d457df]313
[0268aed]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
[4d457df]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)
[60af928]435
[5236449]436    def readCategoryInfo(self):
[60af928]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
[cbcdd2c]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:
[60af928]448            self.master_category_dict = json.load(cat_file)
[5236449]449            self.regenerateModelDict()
[60af928]450
[5236449]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):
[60af928]457        """
[cbcdd2c]458        Regenerates self.by_model_dict which has each model name as the
[60af928]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
[86f88d1]468    def addBackgroundToModel(self, model):
469        """
470        Adds background parameter with default values to the model
471        """
[cbcdd2c]472        assert isinstance(model, QtGui.QStandardItemModel)
[86f88d1]473        checked_list = ['background', '0.001', '-inf', 'inf', '1/cm']
[4d457df]474        FittingUtilities.addCheckedListToModel(model, checked_list)
[86f88d1]475
476    def addScaleToModel(self, model):
477        """
478        Adds scale parameter with default values to the model
479        """
[cbcdd2c]480        assert isinstance(model, QtGui.QStandardItemModel)
[86f88d1]481        checked_list = ['scale', '1.0', '0.0', 'inf', '']
[4d457df]482        FittingUtilities.addCheckedListToModel(model, checked_list)
[86f88d1]483
[0268aed]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
[4d457df]498    def SASModelToQModel(self, model_name, structure_factor=None):
[60af928]499        """
[cbcdd2c]500        Setting model parameters into table based on selected category
[60af928]501        """
[351b53e]502        # TODO - modify for structure factor-only choice
503
[60af928]504        # Crete/overwrite model items
505        self._model_model.clear()
[5236449]506
[60af928]507        kernel_module = generate.load_kernel_module(model_name)
508        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', []))
509
[cbcdd2c]510        # Instantiate the current sasmodel
[5236449]511        self.kernel_module = self.models[model_name]()
512
513        # Explicitly add scale and background with default values
[86f88d1]514        self.addScaleToModel(self._model_model)
515        self.addBackgroundToModel(self._model_model)
[60af928]516
[5236449]517        # Update the QModel
[4d457df]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)
[cd31251]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', []))
[4d457df]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()
[cd31251]531        else:
532            self.addStructureFactor()
533
[5236449]534        # Multishell models need additional treatment
[86f88d1]535        self.addExtraShells()
536
[5236449]537        # Add polydispersity to the model
[86f88d1]538        self.setPolyModel()
[5236449]539        # Add magnetic parameters to the model
[86f88d1]540        self.setMagneticModel()
[5236449]541
[a9b568c]542        # Adjust the table cells width
543        self.lstParams.resizeColumnToContents(0)
544        self.lstParams.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
545
[5236449]546        # Now we claim the model has been loaded
[86f88d1]547        self.model_is_loaded = True
548
[5236449]549        # Update Q Ranges
550        self.updateQRange()
551
[cd31251]552    def updateParamsFromModel(self, item):
553        """
554        Callback method for updating the sasmodel parameters with the GUI values
555        """
[cbcdd2c]556        model_column = item.column()
557        model_row = item.row()
558        name_index = self._model_model.index(model_row, 0)
[cd31251]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())
[cbcdd2c]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
[351b53e]580        # print "%s(%s) => %d" % (parameter_name, property_name, value)
[cbcdd2c]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
[6fd4e36]588    def nameForFittedData(self, name):
[5236449]589        """
[6fd4e36]590        Generate name for the current fit
[5236449]591        """
592        if self.is2D:
593            name += "2d"
594        name = "M%i [%s]" % (self.tab_id, name)
[6fd4e36]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:
[0268aed]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
[6fd4e36]607            self.updateModelIndex(fitted_data)
608        else:
[0268aed]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"
[6fd4e36]614            self.createTheoryIndex(fitted_data)
615
616    def updateModelIndex(self, fitted_data):
617        """
618        Update a QStandardModelIndex containing model data
619        """
[0268aed]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'
[6fd4e36]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        """
[0268aed]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
[6fd4e36]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)
[cbcdd2c]645        self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item)
[5236449]646
[4d457df]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
[b1e36a3]655    def calculateQGridForModel(self):
[86f88d1]656        """
657        Prepare the fitting data object, based on current ModelModel
658        """
[4d457df]659        # Awful API to a backend method.
660        method = self.methodCalculateForData()(data=self.data,
[cbcdd2c]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,
[4d457df]670                              completefn=None,
[cbcdd2c]671                              update_chisqr=True,
[5236449]672                              exception_handler=self.calcException,
[cbcdd2c]673                              source=None)
[4d457df]674
675        calc_thread = threads.deferToThread(method.compute)
676        calc_thread.addCallback(self.methodCompleteForData())
[5236449]677
[cbcdd2c]678    def complete1D(self, return_data):
[5236449]679        """
[4d457df]680        Plot the current 1D data
681        """
[0268aed]682        fitted_plot = self.logic.new1DPlot(return_data)
683        self.calculateResiduals(fitted_plot)
[cbcdd2c]684
685    def complete2D(self, return_data):
686        """
[4d457df]687        Plot the current 2D data
688        """
[6fd4e36]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))
[cbcdd2c]702
[0268aed]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)
[5236449]707
708    def calcException(self, etype, value, tb):
709        """
[b1e36a3]710        Something horrible happened in the deferred.
[5236449]711        """
712        logging.error("".join(traceback.format_exception(etype, value, tb)))
[60af928]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)
[f46f6dc]723        table.resizeColumnsToContents()
724
[60af928]725        # Header
726        header = table.horizontalHeader()
[f46f6dc]727        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
728
729        header.ResizeMode(QtGui.QHeaderView.Interactive)
[b1e36a3]730        # Resize column 0 and 6 to content
[f46f6dc]731        header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
732        header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents)
[60af928]733
734    def setPolyModel(self):
735        """
736        Set polydispersity values
737        """
[86f88d1]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),
[cbcdd2c]748                            str(param.limits[0]), str(param.limits[1]),
[86f88d1]749                            "35", "3", ""]
[4d457df]750            FittingUtilities.addCheckedListToModel(self._poly_model, checked_list)
[86f88d1]751
752            #TODO: Need to find cleaner way to input functions
753            func = QtGui.QComboBox()
[cbcdd2c]754            func.addItems(['rectangle', 'array', 'lognormal', 'gaussian', 'schulz',])
755            func_index = self.lstPoly.model().index(row, 6)
756            self.lstPoly.setIndexWidget(func_index, func)
[86f88d1]757
[4d457df]758        FittingUtilities.addPolyHeadersToModel(self._poly_model)
[60af928]759
760    def setMagneticModel(self):
761        """
762        Set magnetism values on model
763        """
[86f88d1]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]
[4d457df]775            FittingUtilities.addCheckedListToModel(self._magnet_model, checked_list)
[86f88d1]776
[4d457df]777        FittingUtilities.addHeadersToModel(self._magnet_model)
[60af928]778
[cd31251]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
[60af928]788    def addExtraShells(self):
789        """
[f46f6dc]790        Add a combobox for multiple shell display
[60af928]791        """
[4d457df]792        param_name, param_length = FittingUtilities.getMultiplicity(self.model_parameters)
[f46f6dc]793
794        if param_length == 0:
795            return
796
[6f7f652]797        # cell 1: variable name
[f46f6dc]798        item1 = QtGui.QStandardItem(param_name)
799
[60af928]800        func = QtGui.QComboBox()
[b1e36a3]801        # Available range of shells displayed in the combobox
802        func.addItems([str(i) for i in xrange(param_length+1)])
[a9b568c]803
[b1e36a3]804        # Respond to index change
[86f88d1]805        func.currentIndexChanged.connect(self.modifyShellsInList)
[60af928]806
[6f7f652]807        # cell 2: combobox
[f46f6dc]808        item2 = QtGui.QStandardItem()
809        self._model_model.appendRow([item1, item2])
[60af928]810
[6f7f652]811        # Beautify the row:  span columns 2-4
[60af928]812        shell_row = self._model_model.rowCount()
[f46f6dc]813        shell_index = self._model_model.index(shell_row-1, 1)
[86f88d1]814
[4d457df]815        self.lstParams.setIndexWidget(shell_index, func)
[86f88d1]816        self._last_model_row = self._model_model.rowCount()
817
[a9b568c]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
[86f88d1]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
[4d457df]833        FittingUtilities.addShellsToModel(self.model_parameters, self._model_model, index)
[a9b568c]834        self.current_shell_displayed = index
[60af928]835
Note: See TracBrowser for help on using the repository browser.