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

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

Started with unit tests for fitting widget SASVIEW-499

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