source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @ 454670d

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

Allow for multiple datasets to be opened by the fitting perspective

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