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

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

QModel items conversion into SasModel? parameters + data display SASVIEW-535

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