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

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

Added proper handling of structure factor and model dependencies SASVIEW-547

  • Property mode set to 100755
File size: 39.8 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.sascalc.dataloader.data_info import Detector
23from sas.sascalc.dataloader.data_info import Source
24from sas.sasgui.perspectives.fitting.model_thread import Calc1D
25from sas.sasgui.perspectives.fitting.model_thread import Calc2D
26
27from UI.FittingWidgetUI import Ui_FittingWidgetUI
28
29TAB_MAGNETISM = 4
30TAB_POLY = 3
31CATEGORY_DEFAULT = "Choose category..."
32QMIN_DEFAULT = 0.0005
33QMAX_DEFAULT = 0.5
34NPTS_DEFAULT = 50
35
36class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI):
37    """
38    Main widget for selecting form and structure factor models
39    """
40    def __init__(self, manager=None, parent=None, data=None, id=1):
41        """
42
43        :param manager:
44        :param parent:
45        :return:
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
68        # Which tab is this widget displayed in?
69        self.tab_id = id
70
71        # Parameters
72        self.q_range_min = QMIN_DEFAULT
73        self.q_range_max = QMAX_DEFAULT
74        self.npts = NPTS_DEFAULT
75        # Main Data[12]D holder
76        self._data = None
77
78        # Main GUI setup up
79        self.setupUi(self)
80        self.setWindowTitle("Fitting")
81        self.communicate = self.parent.communicate
82
83        # Set the main models
84        # We can't use a single model here, due to restrictions on flattening
85        # the model tree with subclassed QAbstractProxyModel...
86        self._model_model = QtGui.QStandardItemModel()
87        self._poly_model = QtGui.QStandardItemModel()
88        self._magnet_model = QtGui.QStandardItemModel()
89
90        # Set the proxy models for display
91        #   Main display
92        self._model_proxy = QtGui.QSortFilterProxyModel()
93        self._model_proxy.setSourceModel(self._model_model)
94        #self._model_proxy.setFilterRegExp(r"[^()]")
95
96        #   Proxy display
97        self._poly_proxy = QtGui.QSortFilterProxyModel()
98        self._poly_proxy.setSourceModel(self._poly_model)
99        self._poly_proxy.setFilterRegExp(r"[^()]")
100
101        #   Magnetism display
102        self._magnet_proxy = QtGui.QSortFilterProxyModel()
103        self._magnet_proxy.setSourceModel(self._magnet_model)
104        #self._magnet_proxy.setFilterRegExp(r"[^()]")
105
106        # Param model displayed in param list
107        self.lstParams.setModel(self._model_model)
108        #self.lstParams.setModel(self._model_proxy)
109        self.readCategoryInfo()
110        self.model_parameters = None
111        self.lstParams.setAlternatingRowColors(True)
112
113        # Poly model displayed in poly list
114        self.lstPoly.setModel(self._poly_proxy)
115        self.setPolyModel()
116        self.setTableProperties(self.lstPoly)
117
118        # Magnetism model displayed in magnetism list
119        self.lstMagnetic.setModel(self._magnet_model)
120        self.setMagneticModel()
121        self.setTableProperties(self.lstMagnetic)
122
123        # Defaults for the structure factors
124        self.setDefaultStructureCombo()
125
126        # Make structure factor and model CBs disabled
127        self.disableModelCombo()
128        self.disableStructureCombo()
129
130        # Generate the category list for display
131        category_list = sorted(self.master_category_dict.keys())
132        self.cbCategory.addItem(CATEGORY_DEFAULT)
133        self.cbCategory.addItems(category_list)
134        self.cbCategory.addItem("Structure Factor")
135        self.cbCategory.setCurrentIndex(0)
136
137        self._index = data
138        if data is not None:
139            self.data = data
140
141        # Connect signals to controls
142        self.initializeSignals()
143
144        # Initial control state
145        self.initializeControls()
146
147    @property
148    def data(self):
149        return self._data
150
151    @data.setter
152    def data(self, value):
153        """ data setter """
154        # _index contains the QIndex with data
155        self._index = value
156        # _data contains the actual Data[12]D
157        self._data = GuiUtils.dataFromItem(value[0])
158        self.data_is_loaded = True
159        # Tag along functionality
160        self.updateQRange()
161        self.cmdFit.setEnabled(True)
162
163    def acceptsData(self):
164        """ Tells the caller this widget can accept new dataset """
165        return not self.data_is_loaded
166
167    def disableModelCombo(self):
168        """ Disable the combobox """
169        self.cbModel.setEnabled(False)
170        self.label_3.setEnabled(False)
171
172    def enableModelCombo(self):
173        """ Enable the combobox """
174        self.cbModel.setEnabled(True)
175        self.label_3.setEnabled(True)
176
177    def disableStructureCombo(self):
178        """ Disable the combobox """
179        self.cbStructureFactor.setEnabled(False)
180        self.label_4.setEnabled(False)
181
182    def enableStructureCombo(self):
183        """ Enable the combobox """
184        self.cbStructureFactor.setEnabled(True)
185        self.label_4.setEnabled(True)
186
187    def updateQRange(self):
188        """
189        Updates Q Range display
190        """
191        if self.data_is_loaded:
192            self.q_range_min, self.q_range_max, self.npts = self.computeDataRange(self.data)
193        # set Q range labels on the main tab
194        self.lblMinRangeDef.setText(str(self.q_range_min))
195        self.lblMaxRangeDef.setText(str(self.q_range_max))
196        # set Q range labels on the options tab
197        self.txtMaxRange.setText(str(self.q_range_max))
198        self.txtMinRange.setText(str(self.q_range_min))
199        self.txtNpts.setText(str(self.npts))
200
201    def initializeControls(self):
202        """
203        Set initial control enablement
204        """
205        self.cmdFit.setEnabled(False)
206        self.cmdPlot.setEnabled(True)
207        self.chkPolydispersity.setEnabled(True)
208        self.chkPolydispersity.setCheckState(False)
209        self.chk2DView.setEnabled(True)
210        self.chk2DView.setCheckState(False)
211        self.chkMagnetism.setEnabled(False)
212        self.chkMagnetism.setCheckState(False)
213        # Tabs
214        self.tabFitting.setTabEnabled(TAB_POLY, False)
215        self.tabFitting.setTabEnabled(TAB_MAGNETISM, False)
216        self.lblChi2Value.setText("---")
217        # Update Q Ranges
218        self.updateQRange()
219
220    def initializeSignals(self):
221        """
222        Connect GUI element signals
223        """
224        # Comboboxes
225        self.cbStructureFactor.currentIndexChanged.connect(self.onSelectStructureFactor)
226        self.cbCategory.currentIndexChanged.connect(self.onSelectCategory)
227        self.cbModel.currentIndexChanged.connect(self.onSelectModel)
228        # Checkboxes
229        self.chk2DView.toggled.connect(self.toggle2D)
230        self.chkPolydispersity.toggled.connect(self.togglePoly)
231        self.chkMagnetism.toggled.connect(self.toggleMagnetism)
232        # Buttons
233        self.cmdFit.clicked.connect(self.onFit)
234        self.cmdPlot.clicked.connect(self.onPlot)
235        # Line edits
236        self.txtNpts.textChanged.connect(self.onNpts)
237        self.txtMinRange.textChanged.connect(self.onMinRange)
238        self.txtMaxRange.textChanged.connect(self.onMaxRange)
239
240        # Respond to change in parameters from the UI
241        self._model_model.itemChanged.connect(self.updateParamsFromModel)
242        self._poly_model.itemChanged.connect(self.onPolyModelChange)
243        # TODO after the poly_model prototype accepted
244        #self._magnet_model.itemChanged.connect(self.onMagneticModelChange)
245
246    def setDefaultStructureCombo(self):
247        """
248        Fill in the structure factors combo box with defaults
249        """
250        structure_factor_list = self.master_category_dict.pop('Structure Factor')
251        structure_factors = ["None"]
252        self.cbStructureFactor.clear()
253        for (structure_factor, _) in structure_factor_list:
254            structure_factors.append(structure_factor)
255        self.cbStructureFactor.addItems(sorted(structure_factors))
256
257    def onSelectCategory(self):
258        """
259        Select Category from list
260        """
261        category = self.cbCategory.currentText()
262        # Check if the user chose "Choose category entry"
263        if str(category) == CATEGORY_DEFAULT:
264            # if the previous category was not the default, keep it.
265            # Otherwise, just return
266            if self._previous_category_index != 0:
267                self.cbCategory.setCurrentIndex(self._previous_category_index)
268            return
269
270        if category == "Structure Factor":
271            self.disableModelCombo()
272            self.enableStructureCombo()
273            return
274
275        # Safely clear and enable the model combo
276        self.cbModel.blockSignals(True)
277        self.cbModel.clear()
278        self.cbModel.blockSignals(False)
279        self.enableModelCombo()
280        self.disableStructureCombo()
281
282        self._previous_category_index = self.cbCategory.currentIndex()
283        # Retrieve the list of models
284        model_list = self.master_category_dict[str(category)]
285        models = []
286        # Populate the models combobox
287        for (model, _) in model_list:
288            models.append(model)
289        self.cbModel.addItems(sorted(models))
290
291    def onSelectModel(self):
292        """
293        Respond to select Model from list event
294        """
295        model = str(self.cbModel.currentText())
296
297        # SasModel -> QModel
298        self.setModelModel(model)
299
300        if self._index is None:
301            # Create default datasets if no data passed
302            if self.is2D:
303                self.createDefault2dData()
304            else:
305                self.createDefault1dData()
306            # DESIGN: create the theory now or on Plot event?
307            #self.createTheoryIndex()
308        else:
309            # Create datasets and errorbars for current data
310            if self.is2D:
311                self.calculate2DForModel()
312            else:
313                self.calculate1DForModel()
314            # TODO: attach the chart to index
315
316    def onSelectStructureFactor(self):
317        """
318        Select Structure Factor from list
319        """
320        model = str(self.cbModel.currentText())
321        structure = str(self.cbStructureFactor.currentText())
322        self.setModelModel(model, structure_factor=structure)
323
324    def readCategoryInfo(self):
325        """
326        Reads the categories in from file
327        """
328        self.master_category_dict = defaultdict(list)
329        self.by_model_dict = defaultdict(list)
330        self.model_enabled_dict = defaultdict(bool)
331
332        categorization_file = CategoryInstaller.get_user_file()
333        if not os.path.isfile(categorization_file):
334            categorization_file = CategoryInstaller.get_default_file()
335        with open(categorization_file, 'rb') as cat_file:
336            self.master_category_dict = json.load(cat_file)
337            self.regenerateModelDict()
338
339        # Load the model dict
340        models = load_standard_models()
341        for model in models:
342            self.models[model.name] = model
343
344    def regenerateModelDict(self):
345        """
346        Regenerates self.by_model_dict which has each model name as the
347        key and the list of categories belonging to that model
348        along with the enabled mapping
349        """
350        self.by_model_dict = defaultdict(list)
351        for category in self.master_category_dict:
352            for (model, enabled) in self.master_category_dict[category]:
353                self.by_model_dict[model].append(category)
354                self.model_enabled_dict[model] = enabled
355
356    def getIterParams(self, model):
357        """
358        Returns a list of all multi-shell parameters in 'model'
359        """
360        return list(filter(lambda par: "[" in par.name, model.iq_parameters))
361
362    def getMultiplicity(self, model):
363        """
364        Finds out if 'model' has multishell parameters.
365        If so, returns the name of the counter parameter and the number of shells
366        """
367        iter_param = ""
368        iter_length = 0
369
370        iter_params = self.getIterParams(model)
371        # pull out the iterator parameter name and length
372        if iter_params:
373            iter_length = iter_params[0].length
374            iter_param = iter_params[0].length_control
375        return (iter_param, iter_length)
376
377    def addBackgroundToModel(self, model):
378        """
379        Adds background parameter with default values to the model
380        """
381        assert isinstance(model, QtGui.QStandardItemModel)
382        checked_list = ['background', '0.001', '-inf', 'inf', '1/cm']
383        self.addCheckedListToModel(model, checked_list)
384
385    def addScaleToModel(self, model):
386        """
387        Adds scale parameter with default values to the model
388        """
389        assert isinstance(model, QtGui.QStandardItemModel)
390        checked_list = ['scale', '1.0', '0.0', 'inf', '']
391        self.addCheckedListToModel(model, checked_list)
392
393    def addCheckedListToModel(self, model, param_list):
394        assert isinstance(model, QtGui.QStandardItemModel)
395        item_list = [QtGui.QStandardItem(item) for item in param_list]
396        item_list[0].setCheckable(True)
397        model.appendRow(item_list)
398
399    def addHeadersToModel(self, model):
400        """
401        Adds predefined headers to the model
402        """
403        model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Parameter"))
404        model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("Value"))
405        model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Min"))
406        model.setHeaderData(3, QtCore.Qt.Horizontal, QtCore.QVariant("Max"))
407        model.setHeaderData(4, QtCore.Qt.Horizontal, QtCore.QVariant("[Units]"))
408
409    def addPolyHeadersToModel(self, model):
410        """
411        Adds predefined headers to the model
412        """
413        model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Parameter"))
414        model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("PD[ratio]"))
415        model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Min"))
416        model.setHeaderData(3, QtCore.Qt.Horizontal, QtCore.QVariant("Max"))
417        model.setHeaderData(4, QtCore.Qt.Horizontal, QtCore.QVariant("Npts"))
418        model.setHeaderData(5, QtCore.Qt.Horizontal, QtCore.QVariant("Nsigs"))
419        model.setHeaderData(6, QtCore.Qt.Horizontal, QtCore.QVariant("Function"))
420
421    def setModelModel(self, model_name, structure_factor=None):
422        """
423        Setting model parameters into table based on selected category
424        """
425        # Crete/overwrite model items
426        self._model_model.clear()
427
428        kernel_module = generate.load_kernel_module(model_name)
429        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', []))
430
431        # Instantiate the current sasmodel
432        self.kernel_module = self.models[model_name]()
433
434        # Explicitly add scale and background with default values
435        self.addScaleToModel(self._model_model)
436        self.addBackgroundToModel(self._model_model)
437
438        # Update the QModel
439        self.addParametersToModel(self.model_parameters, self._model_model)
440        self.addHeadersToModel(self._model_model)
441
442        # Add structure factor
443        if structure_factor is not None and structure_factor != "None":
444            structure_module = generate.load_kernel_module(structure_factor)
445            structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', []))
446            self.addSimpleParametersToModel(structure_parameters, self._model_model)
447        else:
448            self.addStructureFactor()
449
450        # Multishell models need additional treatment
451        self.addExtraShells()
452
453        # Add polydispersity to the model
454        self.setPolyModel()
455        # Add magnetic parameters to the model
456        self.setMagneticModel()
457
458        # Now we claim the model has been loaded
459        self.model_is_loaded = True
460
461        # Update Q Ranges
462        self.updateQRange()
463
464    def onPolyModelChange(self, item):
465        """
466        Callback method for updating the main model and sasmodel
467        parameters with the GUI values in the polydispersity view
468        """
469        model_column = item.column()
470        model_row = item.row()
471        name_index = self._poly_model.index(model_row, 0)
472        # Extract changed value. Assumes proper validation by QValidator/Delegate
473        value = float(item.text())
474        parameter_name = str(self._poly_model.data(name_index).toPyObject()) # "distribution of sld" etc.
475        if "Distribution of" in parameter_name:
476            parameter_name = parameter_name[16:]
477        property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()) # Value, min, max, etc.
478        print "%s(%s) => %d" % (parameter_name, property_name, value)
479
480        # Update the sasmodel
481        #self.kernel_module.params[parameter_name] = value
482
483        # Reload the main model - may not be required if no variable is shown in main view
484        #model = str(self.cbModel.currentText())
485        #self.setModelModel(model)
486
487        pass # debug anchor
488
489    def updateParamsFromModel(self, item):
490        """
491        Callback method for updating the sasmodel parameters with the GUI values
492        """
493        model_column = item.column()
494        model_row = item.row()
495        name_index = self._model_model.index(model_row, 0)
496
497        if model_column == 0:
498            # Assure we're dealing with checkboxes
499            if not item.isCheckable():
500                return
501            status = item.checkState()
502            # If multiple rows selected - toggle all of them
503            rows = [s.row() for s in self.lstParams.selectionModel().selectedRows()]
504
505            # Switch off signaling from the model to avoid multiple calls
506            self._model_model.blockSignals(True)
507            # Convert to proper indices and set requested enablement
508            items = [self._model_model.item(row, 0).setCheckState(status) for row in rows]
509            self._model_model.blockSignals(False)
510            return
511
512        # Extract changed value. Assumes proper validation by QValidator/Delegate
513        value = float(item.text())
514        parameter_name = str(self._model_model.data(name_index).toPyObject()) # sld, background etc.
515        property_name = str(self._model_model.headerData(1, model_column).toPyObject()) # Value, min, max, etc.
516
517        print "%s(%s) => %d" % (parameter_name, property_name, value)
518        self.kernel_module.params[parameter_name] = value
519
520        # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
521
522        # magnetic params in self.kernel_module.details['M0:parameter_name'] = value
523        # multishell params in self.kernel_module.details[??] = value
524
525    def computeDataRange(self, data):
526        """
527        Compute the minimum and the maximum range of the data
528        return the npts contains in data
529        """
530        assert data is not None
531        assert (isinstance(data, Data1D) or isinstance(data, Data2D))
532        qmin, qmax, npts = None, None, None
533        if isinstance(data, Data1D):
534            try:
535                qmin = min(data.x)
536                qmax = max(data.x)
537                npts = len(data.x)
538            except (ValueError, TypeError):
539                msg = "Unable to find min/max/length of \n data named %s" % \
540                            data.filename
541                raise ValueError, msg
542
543        else:
544            qmin = 0
545            try:
546                x = max(numpy.fabs(data.xmin), numpy.fabs(data.xmax))
547                y = max(numpy.fabs(data.ymin), numpy.fabs(data.ymax))
548            except (ValueError, TypeError):
549                msg = "Unable to find min/max of \n data named %s" % \
550                            data.filename
551                raise ValueError, msg
552            qmax = numpy.sqrt(x * x + y * y)
553            npts = len(data.data)
554        return qmin, qmax, npts
555
556    def addParametersToModel(self, parameters, model):
557        """
558        Update local ModelModel with sasmodel parameters
559        """
560        multishell_parameters = self.getIterParams(parameters)
561        multishell_param_name, _ = self.getMultiplicity(parameters)
562
563        for param in parameters.iq_parameters:
564            # don't include shell parameters
565            if param.name == multishell_param_name:
566                continue
567            # Modify parameter name from <param>[n] to <param>1
568            item_name = param.name
569            if param in multishell_parameters:
570                item_name = self.replaceShellName(param.name, 1)
571
572            item1 = QtGui.QStandardItem(item_name)
573            item1.setCheckable(True)
574            # check for polydisp params
575            if param.polydisperse:
576                poly_item = QtGui.QStandardItem("Polydispersity")
577                item1_1 = QtGui.QStandardItem("Distribution")
578                # Find param in volume_params
579                for p in parameters.form_volume_parameters:
580                    if p.name != param.name:
581                        continue
582                    item1_2 = QtGui.QStandardItem(str(p.default))
583                    item1_3 = QtGui.QStandardItem(str(p.limits[0]))
584                    item1_4 = QtGui.QStandardItem(str(p.limits[1]))
585                    item1_5 = QtGui.QStandardItem(p.units)
586                    poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
587                    break
588                # Add the polydisp item as a child
589                item1.appendRow([poly_item])
590            # Param values
591            item2 = QtGui.QStandardItem(str(param.default))
592            # TODO: the error column.
593            # Either add a proxy model or a custom view delegate
594            #item_err = QtGui.QStandardItem()
595            item3 = QtGui.QStandardItem(str(param.limits[0]))
596            item4 = QtGui.QStandardItem(str(param.limits[1]))
597            item5 = QtGui.QStandardItem(param.units)
598            model.appendRow([item1, item2, item3, item4, item5])
599
600        # Update the counter used for multishell display
601        self._last_model_row = self._model_model.rowCount()
602
603    def addSimpleParametersToModel(self, parameters, model):
604        """
605        Update local ModelModel with sasmodel parameters
606        """
607        for param in parameters.iq_parameters:
608            # Modify parameter name from <param>[n] to <param>1
609            item_name = param.name
610            item1 = QtGui.QStandardItem(item_name)
611            item1.setCheckable(True)
612            # Param values
613            item2 = QtGui.QStandardItem(str(param.default))
614            # TODO: the error column.
615            # Either add a proxy model or a custom view delegate
616            #item_err = QtGui.QStandardItem()
617            item3 = QtGui.QStandardItem(str(param.limits[0]))
618            item4 = QtGui.QStandardItem(str(param.limits[1]))
619            item5 = QtGui.QStandardItem(param.units)
620            model.appendRow([item1, item2, item3, item4, item5])
621
622        # Update the counter used for multishell display
623        self._last_model_row = self._model_model.rowCount()
624
625    def createDefault1dData(self):
626        """
627        Create default data for fitting perspective
628        Only when the page is on theory mode.
629        """
630        x = numpy.linspace(start=self.q_range_min, stop=self.q_range_max,
631                           num=self.npts, endpoint=True)
632        self._data = Data1D(x=x)
633        self._data.xaxis('\\rm{Q}', "A^{-1}")
634        self._data.yaxis('\\rm{Intensity}', "cm^{-1}")
635        self._data.is_data = False
636        self._data.id = str(self.tab_id) + " data"
637        self._data.group_id = str(self.tab_id) + " Model1D"
638
639    def createDefault2dData(self):
640        """
641        Create 2D data by default
642        Only when the page is on theory mode.
643        """
644        self._data = Data2D()
645        qmax = self.q_range_max / numpy.sqrt(2)
646        self._data.xaxis('\\rm{Q_{x}}', 'A^{-1}')
647        self._data.yaxis('\\rm{Q_{y}}', 'A^{-1}')
648        self._data.is_data = False
649        self._data.id = str(self.tab_id) + " data"
650        self._data.group_id = str(self.tab_id) + " Model2D"
651
652        # Default detector
653        self._data.detector.append(Detector())
654        index = len(self._data.detector) - 1
655        self._data.detector[index].distance = 8000   # mm
656        self._data.source.wavelength = 6             # A
657        self._data.detector[index].pixel_size.x = 5  # mm
658        self._data.detector[index].pixel_size.y = 5  # mm
659        self._data.detector[index].beam_center.x = qmax
660        self._data.detector[index].beam_center.y = qmax
661        # theory default: assume the beam
662        #center is located at the center of sqr detector
663        xmax = qmax
664        xmin = -qmax
665        ymax = qmax
666        ymin = -qmax
667        qstep = self.npts
668
669        x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
670        y = numpy.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True)
671        # Use data info instead
672        new_x = numpy.tile(x, (len(y), 1))
673        new_y = numpy.tile(y, (len(x), 1))
674        new_y = new_y.swapaxes(0, 1)
675
676        # all data required in 1d array
677        qx_data = new_x.flatten()
678        qy_data = new_y.flatten()
679        q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
680
681        # set all True (standing for unmasked) as default
682        mask = numpy.ones(len(qx_data), dtype=bool)
683        # calculate the range of qx and qy: this way,
684        # it is a little more independent
685        # store x and y bin centers in q space
686        x_bins = x
687        y_bins = y
688
689        self._data.source = Source()
690        self._data.data = numpy.ones(len(mask))
691        self._data.err_data = numpy.ones(len(mask))
692        self._data.qx_data = qx_data
693        self._data.qy_data = qy_data
694        self._data.q_data = q_data
695        self._data.mask = mask
696        self._data.x_bins = x_bins
697        self._data.y_bins = y_bins
698        # max and min taking account of the bin sizes
699        self._data.xmin = xmin
700        self._data.xmax = xmax
701        self._data.ymin = ymin
702        self._data.ymax = ymax
703
704    def createTheoryIndex(self):
705        """
706        Create a QStandardModelIndex containing default model data
707        """
708        name = self.kernel_module.name
709        if self.is2D:
710            name += "2d"
711        name = "M%i [%s]" % (self.tab_id, name)
712        new_item = GuiUtils.createModelItemWithPlot(QtCore.QVariant(self.data), name=name)
713        # Notify the GUI manager so it can update the theory model in DataExplorer
714        self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item)
715
716    def onFit(self):
717        """
718        Perform fitting on the current data
719        """
720        #self.calculate1DForModel()
721        pass
722
723    def onPlot(self):
724        """
725        Plot the current set of data
726        """
727        # TODO: reimplement basepage.py/_update_paramv_on_fit
728        if self.data is None or not self._data.is_data:
729            self.createDefault2dData() if self.is2D else self.createDefault1dData()
730        self.calculate2DForModel() if self.is2D else self.calculate1DForModel()
731
732    def onNpts(self, text):
733        """
734        Callback for number of points line edit update
735        """
736        # assumes type/value correctness achieved with QValidator
737        try:
738            self.npts = int(text)
739        except:
740            pass
741
742    def onMinRange(self, text):
743        """
744        Callback for minimum range of points line edit update
745        """
746        # assumes type/value correctness achieved with QValidator
747        try:
748            self.q_range_min = float(text)
749        except:
750            pass
751        # set Q range labels on the main tab
752        self.lblMinRangeDef.setText(str(self.q_range_min))
753
754    def onMaxRange(self, text):
755        """
756        Callback for maximum range of points line edit update
757        """
758        # assumes type/value correctness achieved with QValidator
759        try:
760            self.q_range_max = float(text)
761        except:
762            pass
763        # set Q range labels on the main tab
764        self.lblMaxRangeDef.setText(str(self.q_range_max))
765
766    def calculate1DForModel(self):
767        """
768        Prepare the fitting data object, based on current ModelModel
769        """
770        self.calc_1D = Calc1D(data=self.data,
771                              model=self.kernel_module,
772                              page_id=0,
773                              qmin=self.q_range_min,
774                              qmax=self.q_range_max,
775                              smearer=None,
776                              state=None,
777                              weight=None,
778                              fid=None,
779                              toggle_mode_on=False,
780                              completefn=self.complete1D,
781                              update_chisqr=True,
782                              exception_handler=self.calcException,
783                              source=None)
784        # Instead of starting the thread with
785        #   self.calc_1D.queue()
786        # let's try running the async request
787        calc_thread = threads.deferToThread(self.calc_1D.compute)
788        calc_thread.addCallback(self.complete1D)
789
790    def complete1D(self, return_data):
791        """
792        Plot the current data
793        """
794        # Unpack return data from Calc1D
795        x, y, page_id, state, weight,\
796        fid, toggle_mode_on, \
797        elapsed, index, model,\
798        data, update_chisqr, source = return_data
799
800        # Create the new plot
801        new_plot = Data1D(x=x, y=y)
802        new_plot.is_data = False
803        new_plot.dy = numpy.zeros(len(y))
804        _yaxis, _yunit = data.get_yaxis()
805        _xaxis, _xunit = data.get_xaxis()
806        new_plot.title = data.name
807
808        new_plot.group_id = data.group_id
809        if new_plot.group_id == None:
810            new_plot.group_id = data.group_id
811        new_plot.id = str(self.tab_id) + " " + data.name
812        new_plot.name = model.name + " [" + data.name + "]"
813        new_plot.xaxis(_xaxis, _xunit)
814        new_plot.yaxis(_yaxis, _yunit)
815
816        # Assign the new Data1D object-wide
817        self._data = new_plot
818        self.createTheoryIndex()
819
820        #output=self._cal_chisqr(data=data,
821        #                        fid=fid,
822        #                        weight=weight,
823        #                        page_id=page_id,
824        #                        index=index)
825
826    def calculate2DForModel(self):
827        """
828        Prepare the fitting data object, based on current ModelModel
829        """
830        self.calc_2D = Calc2D(data=self.data,
831                              model=self.kernel_module,
832                              page_id=0,
833                              qmin=self.q_range_min,
834                              qmax=self.q_range_max,
835                              smearer=None,
836                              state=None,
837                              weight=None,
838                              fid=None,
839                              toggle_mode_on=False,
840                              completefn=self.complete2D,
841                              update_chisqr=True,
842                              exception_handler=self.calcException,
843                              source=None)
844
845        # Instead of starting the thread with
846        #    self.calc_2D.queue()
847        # let's try running the async request
848        calc_thread = threads.deferToThread(self.calc_2D.compute)
849        calc_thread.addCallback(self.complete2D)
850
851    def complete2D(self, return_data):
852        """
853        Plot the current data
854        Should be a rewrite of fitting.py/_complete2D
855        """
856        image, data, page_id, model, state, toggle_mode_on,\
857        elapsed, index, fid, qmin, qmax, weight, \
858        update_chisqr, source = return_data
859
860        numpy.nan_to_num(image)
861        new_plot = Data2D(image=image, err_image=data.err_data)
862        new_plot.name = model.name + '2d'
863        new_plot.title = "Analytical model 2D "
864        new_plot.id = str(page_id) + " " + data.name
865        new_plot.group_id = str(page_id) + " Model2D"
866        new_plot.detector = data.detector
867        new_plot.source = data.source
868        new_plot.is_data = False
869        new_plot.qx_data = data.qx_data
870        new_plot.qy_data = data.qy_data
871        new_plot.q_data = data.q_data
872        new_plot.mask = data.mask
873        ## plot boundaries
874        new_plot.ymin = data.ymin
875        new_plot.ymax = data.ymax
876        new_plot.xmin = data.xmin
877        new_plot.xmax = data.xmax
878
879        title = data.title
880
881        new_plot.is_data = False
882        if data.is_data:
883            data_name = str(data.name)
884        else:
885            data_name = str(model.__class__.__name__) + '2d'
886
887        if len(title) > 1:
888            new_plot.title = "Model2D for %s " % model.name + data_name
889        new_plot.name = model.name + " [" + \
890                                    data_name + "]"
891
892        # Assign the new Data2D object-wide
893        self._data = new_plot
894        self.createTheoryIndex()
895
896        #output=self._cal_chisqr(data=data,
897        #                        weight=weight,
898        #                        fid=fid,
899        #                        page_id=page_id,
900        #                        index=index)
901        #    self._plot_residuals(page_id=page_id, data=data, fid=fid,
902        #                            index=index, weight=weight)
903
904    def calcException(self, etype, value, tb):
905        """
906        Something horrible happened in the deferred. Cry me a river.
907        """
908        logging.error("".join(traceback.format_exception(etype, value, tb)))
909        msg = traceback.format_exception(etype, value, tb, limit=1)
910
911    def replaceShellName(self, param_name, value):
912        """
913        Updates parameter name from <param_name>[n_shell] to <param_name>value
914        """
915        assert '[' in param_name
916        return param_name[:param_name.index('[')]+str(value)
917
918    def setTableProperties(self, table):
919        """
920        Setting table properties
921        """
922        # Table properties
923        table.verticalHeader().setVisible(False)
924        table.setAlternatingRowColors(True)
925        table.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
926        table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
927        table.resizeColumnsToContents()
928
929        # Header
930        header = table.horizontalHeader()
931        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
932
933        header.ResizeMode(QtGui.QHeaderView.Interactive)
934        header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
935        header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents)
936
937    def setPolyModel(self):
938        """
939        Set polydispersity values
940        """
941        if not self.model_parameters:
942            return
943        self._poly_model.clear()
944        for row, param in enumerate(self.model_parameters.form_volume_parameters):
945            # Counters should not be included
946            if not param.polydisperse:
947                continue
948
949            # Potential multishell params
950            checked_list = ["Distribution of "+param.name, str(param.default),
951                            str(param.limits[0]), str(param.limits[1]),
952                            "35", "3", ""]
953            self.addCheckedListToModel(self._poly_model, checked_list)
954
955            #TODO: Need to find cleaner way to input functions
956            func = QtGui.QComboBox()
957            func.addItems(['rectangle', 'array', 'lognormal', 'gaussian', 'schulz',])
958            func_index = self.lstPoly.model().index(row, 6)
959            self.lstPoly.setIndexWidget(func_index, func)
960
961        self.addPolyHeadersToModel(self._poly_model)
962
963    def setMagneticModel(self):
964        """
965        Set magnetism values on model
966        """
967        if not self.model_parameters:
968            return
969        self._magnet_model.clear()
970        for param in self.model_parameters.call_parameters:
971            if param.type != "magnetic":
972                continue
973            checked_list = [param.name,
974                            str(param.default),
975                            str(param.limits[0]),
976                            str(param.limits[1]),
977                            param.units]
978            self.addCheckedListToModel(self._magnet_model, checked_list)
979
980        self.addHeadersToModel(self._magnet_model)
981
982    def addStructureFactor(self):
983        """
984        Add structure factors to the list of parameters
985        """
986        if self.kernel_module.is_form_factor:
987            self.enableStructureCombo()
988        else:
989            self.disableStructureCombo()
990
991    def addExtraShells(self):
992        """
993        Add a combobox for multiple shell display
994        """
995        param_name, param_length = self.getMultiplicity(self.model_parameters)
996
997        if param_length == 0:
998            return
999
1000        # cell 1: variable name
1001        item1 = QtGui.QStandardItem(param_name)
1002
1003        func = QtGui.QComboBox()
1004        func.addItems([str(i+1) for i in xrange(param_length)])
1005        func.currentIndexChanged.connect(self.modifyShellsInList)
1006
1007        # cell 2: combobox
1008        item2 = QtGui.QStandardItem()
1009        self._model_model.appendRow([item1, item2])
1010
1011        # Beautify the row:  span columns 2-4
1012        shell_row = self._model_model.rowCount()
1013        shell_index = self._model_model.index(shell_row-1, 1)
1014        self.lstParams.setIndexWidget(shell_index, func)
1015
1016        self._last_model_row = self._model_model.rowCount()
1017
1018
1019    def modifyShellsInList(self, index):
1020        """
1021        Add/remove additional multishell parameters
1022        """
1023        # Find row location of the combobox
1024        last_row = self._last_model_row
1025        remove_rows = self._model_model.rowCount() - last_row
1026
1027        if remove_rows > 1:
1028            self._model_model.removeRows(last_row, remove_rows)
1029
1030        multishell_parameters = self.getIterParams(self.model_parameters)
1031
1032        for i in xrange(index):
1033            for par in multishell_parameters:
1034                param_name = self.replaceShellName(par.name, i+2)
1035                #param_name = str(p.name) + str(i+2)
1036                item1 = QtGui.QStandardItem(param_name)
1037                item1.setCheckable(True)
1038                # check for polydisp params
1039                if par.polydisperse:
1040                    poly_item = QtGui.QStandardItem("Polydispersity")
1041                    item1_1 = QtGui.QStandardItem("Distribution")
1042                    # Find param in volume_params
1043                    for p in self.model_parameters.form_volume_parameters:
1044                        if p.name != par.name:
1045                            continue
1046                        item1_2 = QtGui.QStandardItem(str(p.default))
1047                        item1_3 = QtGui.QStandardItem(str(p.limits[0]))
1048                        item1_4 = QtGui.QStandardItem(str(p.limits[1]))
1049                        item1_5 = QtGui.QStandardItem(p.units)
1050                        poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
1051                        break
1052                    item1.appendRow([poly_item])
1053
1054                item2 = QtGui.QStandardItem(str(par.default))
1055                item3 = QtGui.QStandardItem(str(par.limits[0]))
1056                item4 = QtGui.QStandardItem(str(par.limits[1]))
1057                item5 = QtGui.QStandardItem(par.units)
1058                self._model_model.appendRow([item1, item2, item3, item4, item5])
1059
1060    def togglePoly(self, isChecked):
1061        """
1062        Enable/disable the polydispersity tab
1063        """
1064        self.tabFitting.setTabEnabled(TAB_POLY, isChecked)
1065
1066    def toggleMagnetism(self, isChecked):
1067        """
1068        Enable/disable the magnetism tab
1069        """
1070        self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked)
1071
1072    def toggle2D(self, isChecked):
1073        """
1074        Enable/disable the controls dependent on 1D/2D data instance
1075        """
1076        self.chkMagnetism.setEnabled(isChecked)
1077        self.is2D = isChecked
1078
Note: See TracBrowser for help on using the repository browser.