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

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

More unit tests for fitting SASVIEW-499

  • Property mode set to 100755
File size: 26.1 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.sasgui.perspectives.fitting.model_thread import Calc1D
23from sas.sasgui.perspectives.fitting.model_thread import Calc2D
24
25from UI.FittingWidgetUI import Ui_FittingWidgetUI
26from sas.qtgui.Perspectives.Fitting.FittingLogic import FittingLogic
27from sas.qtgui.Perspectives.Fitting import FittingUtilities
28
29TAB_MAGNETISM = 4
30TAB_POLY = 3
31CATEGORY_DEFAULT = "Choose category..."
32CATEGORY_STRUCTURE = "Structure Factor"
33STRUCTURE_DEFAULT = "None"
34QMIN_DEFAULT = 0.0005
35QMAX_DEFAULT = 0.5
36NPTS_DEFAULT = 50
37
38class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI):
39    """
40    Main widget for selecting form and structure factor models
41    """
42    def __init__(self, parent=None, data=None, id=1):
43
44        super(FittingWidget, self).__init__()
45
46        # Necessary globals
47        self.parent = parent
48        # SasModel is loaded
49        self.model_is_loaded = False
50        # Data[12]D passed and set
51        self.data_is_loaded = False
52        # Current SasModel in view
53        self.kernel_module = None
54        # Current SasModel view dimension
55        self.is2D = False
56        # Current SasModel is multishell
57        self.model_has_shells = False
58        # Utility variable to enable unselectable option in category combobox
59        self._previous_category_index = 0
60        # Utility variable for multishell display
61        self._last_model_row = 0
62        # Dictionary of {model name: model class} for the current category
63        self.models = {}
64
65        # Which tab is this widget displayed in?
66        self.tab_id = id
67
68        # Range parameters
69        self.q_range_min = QMIN_DEFAULT
70        self.q_range_max = QMAX_DEFAULT
71        self.npts = NPTS_DEFAULT
72
73        # Main Data[12]D holder
74        self.logic = FittingLogic(data=data)
75
76        # Main GUI setup up
77        self.setupUi(self)
78        self.setWindowTitle("Fitting")
79        self.communicate = self.parent.communicate
80
81        # Set the main models
82        # We can't use a single model here, due to restrictions on flattening
83        # the model tree with subclassed QAbstractProxyModel...
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(CATEGORY_STRUCTURE)
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.logic.data
131
132    @data.setter
133    def data(self, value):
134        """ data setter """
135        assert isinstance(value[0], QtGui.QStandardItem)
136        # _index contains the QIndex with data
137        self._index = value
138        # Update logics with data items
139        self.logic.data = GuiUtils.dataFromItem(value[0])
140
141        self.data_is_loaded = True
142        # Tag along functionality
143        self.updateQRange()
144        self.cmdFit.setEnabled(True)
145
146    def acceptsData(self):
147        """ Tells the caller this widget can accept new dataset """
148        return not self.data_is_loaded
149
150    def disableModelCombo(self):
151        """ Disable the combobox """
152        self.cbModel.setEnabled(False)
153        self.label_3.setEnabled(False)
154
155    def enableModelCombo(self):
156        """ Enable the combobox """
157        self.cbModel.setEnabled(True)
158        self.label_3.setEnabled(True)
159
160    def disableStructureCombo(self):
161        """ Disable the combobox """
162        self.cbStructureFactor.setEnabled(False)
163        self.label_4.setEnabled(False)
164
165    def enableStructureCombo(self):
166        """ Enable the combobox """
167        self.cbStructureFactor.setEnabled(True)
168        self.label_4.setEnabled(True)
169
170    def updateQRange(self):
171        """
172        Updates Q Range display
173        """
174        if self.data_is_loaded:
175            self.q_range_min, self.q_range_max, self.npts = self.logic.computeDataRange()
176        # set Q range labels on the main tab
177        self.lblMinRangeDef.setText(str(self.q_range_min))
178        self.lblMaxRangeDef.setText(str(self.q_range_max))
179        # set Q range labels on the options tab
180        self.txtMaxRange.setText(str(self.q_range_max))
181        self.txtMinRange.setText(str(self.q_range_min))
182        self.txtNpts.setText(str(self.npts))
183
184    def initializeControls(self):
185        """
186        Set initial control enablement
187        """
188        self.cmdFit.setEnabled(False)
189        self.cmdPlot.setEnabled(True)
190        self.chkPolydispersity.setEnabled(True)
191        self.chkPolydispersity.setCheckState(False)
192        self.chk2DView.setEnabled(True)
193        self.chk2DView.setCheckState(False)
194        self.chkMagnetism.setEnabled(False)
195        self.chkMagnetism.setCheckState(False)
196        # Tabs
197        self.tabFitting.setTabEnabled(TAB_POLY, False)
198        self.tabFitting.setTabEnabled(TAB_MAGNETISM, False)
199        self.lblChi2Value.setText("---")
200        # Update Q Ranges
201        self.updateQRange()
202
203    def initializeSignals(self):
204        """
205        Connect GUI element signals
206        """
207        # Comboboxes
208        self.cbStructureFactor.currentIndexChanged.connect(self.onSelectStructureFactor)
209        self.cbCategory.currentIndexChanged.connect(self.onSelectCategory)
210        self.cbModel.currentIndexChanged.connect(self.onSelectModel)
211        # Checkboxes
212        self.chk2DView.toggled.connect(self.toggle2D)
213        self.chkPolydispersity.toggled.connect(self.togglePoly)
214        self.chkMagnetism.toggled.connect(self.toggleMagnetism)
215        # Buttons
216        self.cmdFit.clicked.connect(self.onFit)
217        self.cmdPlot.clicked.connect(self.onPlot)
218        # Line edits
219        self.txtNpts.textChanged.connect(self.onNpts)
220        self.txtMinRange.textChanged.connect(self.onMinRange)
221        self.txtMaxRange.textChanged.connect(self.onMaxRange)
222
223        # Respond to change in parameters from the UI
224        self._model_model.itemChanged.connect(self.updateParamsFromModel)
225        self._poly_model.itemChanged.connect(self.onPolyModelChange)
226        # TODO after the poly_model prototype accepted
227        #self._magnet_model.itemChanged.connect(self.onMagneticModelChange)
228
229    def setDefaultStructureCombo(self):
230        """
231        Fill in the structure factors combo box with defaults
232        """
233        structure_factor_list = self.master_category_dict.pop(CATEGORY_STRUCTURE)
234        factors = [factor[0] for factor in structure_factor_list]
235        factors.insert(0, STRUCTURE_DEFAULT)
236        self.cbStructureFactor.clear()
237        self.cbStructureFactor.addItems(sorted(factors))
238
239    def onSelectCategory(self):
240        """
241        Select Category from list
242        """
243        category = str(self.cbCategory.currentText())
244        # Check if the user chose "Choose category entry"
245        if category == CATEGORY_DEFAULT:
246            # if the previous category was not the default, keep it.
247            # Otherwise, just return
248            if self._previous_category_index != 0:
249                # We need to block signals, or else state changes on perceived unchanged conditions
250                self.cbCategory.blockSignals(True)
251                self.cbCategory.setCurrentIndex(self._previous_category_index)
252                self.cbCategory.blockSignals(False)
253            return
254
255        if category == CATEGORY_STRUCTURE:
256            self.disableModelCombo()
257            self.enableStructureCombo()
258            return
259
260        # Safely clear and enable the model combo
261        self.cbModel.blockSignals(True)
262        self.cbModel.clear()
263        self.cbModel.blockSignals(False)
264        self.enableModelCombo()
265        self.disableStructureCombo()
266
267        self._previous_category_index = self.cbCategory.currentIndex()
268        # Retrieve the list of models
269        model_list = self.master_category_dict[category]
270        models = []
271        # Populate the models combobox
272        self.cbModel.addItems(sorted([model for (model,_) in model_list]))
273
274    def createDefaultDataset(self):
275        """
276        Generate default Dataset 1D/2D for the given model
277        """
278        # Create default datasets if no data passed
279        if self.is2D:
280            qmax = self.q_range_max/numpy.sqrt(2)
281            qstep = self.npts
282            self.logic.createDefault2dData(qmax, qstep, self.tab_id)
283        else:
284            interval = numpy.linspace(start=self.q_range_min, stop=self.q_range_max,
285                        num=self.npts, endpoint=True)
286            self.logic.createDefault1dData(interval, self.tab_id)
287
288    def onSelectModel(self):
289        """
290        Respond to select Model from list event
291        """
292        model = str(self.cbModel.currentText())
293
294        # Reset structure factor
295        self.cbStructureFactor.setCurrentIndex(0)
296
297        # SasModel -> QModel
298        self.SASModelToQModel(model)
299
300        if self._index is None:
301            # Create default datasets if no data passed
302            self.createDefaultDataset()
303        else:
304            self.calculateDataForModel()
305
306    def onSelectStructureFactor(self):
307        """
308        Select Structure Factor from list
309        """
310        model = str(self.cbModel.currentText())
311        structure = str(self.cbStructureFactor.currentText())
312        self.SASModelToQModel(model, structure_factor=structure)
313
314    def readCategoryInfo(self):
315        """
316        Reads the categories in from file
317        """
318        self.master_category_dict = defaultdict(list)
319        self.by_model_dict = defaultdict(list)
320        self.model_enabled_dict = defaultdict(bool)
321
322        categorization_file = CategoryInstaller.get_user_file()
323        if not os.path.isfile(categorization_file):
324            categorization_file = CategoryInstaller.get_default_file()
325        with open(categorization_file, 'rb') as cat_file:
326            self.master_category_dict = json.load(cat_file)
327            self.regenerateModelDict()
328
329        # Load the model dict
330        models = load_standard_models()
331        for model in models:
332            self.models[model.name] = model
333
334    def regenerateModelDict(self):
335        """
336        Regenerates self.by_model_dict which has each model name as the
337        key and the list of categories belonging to that model
338        along with the enabled mapping
339        """
340        self.by_model_dict = defaultdict(list)
341        for category in self.master_category_dict:
342            for (model, enabled) in self.master_category_dict[category]:
343                self.by_model_dict[model].append(category)
344                self.model_enabled_dict[model] = enabled
345
346    def addBackgroundToModel(self, model):
347        """
348        Adds background parameter with default values to the model
349        """
350        assert isinstance(model, QtGui.QStandardItemModel)
351        checked_list = ['background', '0.001', '-inf', 'inf', '1/cm']
352        FittingUtilities.addCheckedListToModel(model, checked_list)
353
354    def addScaleToModel(self, model):
355        """
356        Adds scale parameter with default values to the model
357        """
358        assert isinstance(model, QtGui.QStandardItemModel)
359        checked_list = ['scale', '1.0', '0.0', 'inf', '']
360        FittingUtilities.addCheckedListToModel(model, checked_list)
361
362    def SASModelToQModel(self, model_name, structure_factor=None):
363        """
364        Setting model parameters into table based on selected category
365        """
366        # TODO - modify for structure factor-only choice
367
368        # Crete/overwrite model items
369        self._model_model.clear()
370
371        kernel_module = generate.load_kernel_module(model_name)
372        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', []))
373
374        # Instantiate the current sasmodel
375        self.kernel_module = self.models[model_name]()
376
377        # Explicitly add scale and background with default values
378        self.addScaleToModel(self._model_model)
379        self.addBackgroundToModel(self._model_model)
380
381        # Update the QModel
382        FittingUtilities.addParametersToModel(self.model_parameters, self._model_model)
383        # Update the counter used for multishell display
384        self._last_model_row = self._model_model.rowCount()
385
386        FittingUtilities.addHeadersToModel(self._model_model)
387
388        # Add structure factor
389        if structure_factor is not None and structure_factor != "None":
390            structure_module = generate.load_kernel_module(structure_factor)
391            structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', []))
392            FittingUtilities.addSimpleParametersToModel(structure_parameters, self._model_model)
393            # Update the counter used for multishell display
394            self._last_model_row = self._model_model.rowCount()
395        else:
396            self.addStructureFactor()
397
398        # Multishell models need additional treatment
399        self.addExtraShells()
400
401        # Add polydispersity to the model
402        self.setPolyModel()
403        # Add magnetic parameters to the model
404        self.setMagneticModel()
405
406        # Now we claim the model has been loaded
407        self.model_is_loaded = True
408
409        # Update Q Ranges
410        self.updateQRange()
411
412    def onPolyModelChange(self, item):
413        """
414        Callback method for updating the main model and sasmodel
415        parameters with the GUI values in the polydispersity view
416        """
417        model_column = item.column()
418        model_row = item.row()
419        name_index = self._poly_model.index(model_row, 0)
420        # Extract changed value. Assumes proper validation by QValidator/Delegate
421        value = float(item.text())
422        parameter_name = str(self._poly_model.data(name_index).toPyObject()) # "distribution of sld" etc.
423        if "Distribution of" in parameter_name:
424            parameter_name = parameter_name[16:]
425        property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()) # Value, min, max, etc.
426        # print "%s(%s) => %d" % (parameter_name, property_name, value)
427
428        # Update the sasmodel
429        #self.kernel_module.params[parameter_name] = value
430
431        # Reload the main model - may not be required if no variable is shown in main view
432        #model = str(self.cbModel.currentText())
433        #self.SASModelToQModel(model)
434
435        pass # debug anchor
436
437    def updateParamsFromModel(self, item):
438        """
439        Callback method for updating the sasmodel parameters with the GUI values
440        """
441        model_column = item.column()
442        model_row = item.row()
443        name_index = self._model_model.index(model_row, 0)
444
445        if model_column == 0:
446            # Assure we're dealing with checkboxes
447            if not item.isCheckable():
448                return
449            status = item.checkState()
450            # If multiple rows selected - toggle all of them
451            rows = [s.row() for s in self.lstParams.selectionModel().selectedRows()]
452
453            # Switch off signaling from the model to avoid multiple calls
454            self._model_model.blockSignals(True)
455            # Convert to proper indices and set requested enablement
456            items = [self._model_model.item(row, 0).setCheckState(status) for row in rows]
457            self._model_model.blockSignals(False)
458            return
459
460        # Extract changed value. Assumes proper validation by QValidator/Delegate
461        value = float(item.text())
462        parameter_name = str(self._model_model.data(name_index).toPyObject()) # sld, background etc.
463        property_name = str(self._model_model.headerData(1, model_column).toPyObject()) # Value, min, max, etc.
464
465        # print "%s(%s) => %d" % (parameter_name, property_name, value)
466        self.kernel_module.params[parameter_name] = value
467
468        # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
469
470        # magnetic params in self.kernel_module.details['M0:parameter_name'] = value
471        # multishell params in self.kernel_module.details[??] = value
472
473
474    def createTheoryIndex(self):
475        """
476        Create a QStandardModelIndex containing default model data
477        """
478        name = self.kernel_module.name
479        if self.is2D:
480            name += "2d"
481        name = "M%i [%s]" % (self.tab_id, name)
482        new_item = GuiUtils.createModelItemWithPlot(QtCore.QVariant(self.data), name=name)
483        # Notify the GUI manager so it can update the theory model in DataExplorer
484        self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item)
485
486    def onFit(self):
487        """
488        Perform fitting on the current data
489        """
490        #self.calculate1DForModel()
491        pass
492
493    def onPlot(self):
494        """
495        Plot the current set of data
496        """
497        # TODO: reimplement basepage.py/_update_paramv_on_fit
498        if self.data is None or not self.data.is_data:
499            self.createDefaultDataset()
500        self.calculateDataForModel()
501
502    def onNpts(self, text):
503        """
504        Callback for number of points line edit update
505        """
506        # assumes type/value correctness achieved with QValidator
507        try:
508            self.npts = int(text)
509        except:
510            pass
511
512    def onMinRange(self, text):
513        """
514        Callback for minimum range of points line edit update
515        """
516        # assumes type/value correctness achieved with QValidator
517        try:
518            self.q_range_min = float(text)
519        except:
520            pass
521        # set Q range labels on the main tab
522        self.lblMinRangeDef.setText(str(self.q_range_min))
523
524    def onMaxRange(self, text):
525        """
526        Callback for maximum range of points line edit update
527        """
528        # assumes type/value correctness achieved with QValidator
529        try:
530            self.q_range_max = float(text)
531        except:
532            pass
533        # set Q range labels on the main tab
534        self.lblMaxRangeDef.setText(str(self.q_range_max))
535
536    def methodCalculateForData(self):
537        '''return the method for data calculation'''
538        return Calc1D if isinstance(self.data, Data1D) else Calc2D
539
540    def methodCompleteForData(self):
541        '''return the method for result parsin on calc complete '''
542        return self.complete1D if isinstance(self.data, Data1D) else self.complete2D
543
544    def calculateDataForModel(self):
545        """
546        Prepare the fitting data object, based on current ModelModel
547        """
548        # Awful API to a backend method.
549        method = self.methodCalculateForData()(data=self.data,
550                              model=self.kernel_module,
551                              page_id=0,
552                              qmin=self.q_range_min,
553                              qmax=self.q_range_max,
554                              smearer=None,
555                              state=None,
556                              weight=None,
557                              fid=None,
558                              toggle_mode_on=False,
559                              completefn=None,
560                              update_chisqr=True,
561                              exception_handler=self.calcException,
562                              source=None)
563
564        calc_thread = threads.deferToThread(method.compute)
565        calc_thread.addCallback(self.methodCompleteForData())
566
567    def complete1D(self, return_data):
568        """
569        Plot the current 1D data
570        """
571        self.logic.new1DPlot(return_data)
572        self.createTheoryIndex()
573
574        #output=self._cal_chisqr(data=data,
575        #                        fid=fid,
576        #                        weight=weight,
577        #                        page_id=page_id,
578        #                        index=index)
579
580    def complete2D(self, return_data):
581        """
582        Plot the current 2D data
583        """
584        self.logic.new2DPlot(return_data)
585        self.createTheoryIndex()
586
587        #output=self._cal_chisqr(data=data,
588        #                        weight=weight,
589        #                        fid=fid,
590        #                        page_id=page_id,
591        #                        index=index)
592        #    self._plot_residuals(page_id=page_id, data=data, fid=fid,
593        #                            index=index, weight=weight)
594
595    def calcException(self, etype, value, tb):
596        """
597        Something horrible happened in the deferred. Cry me a river.
598        """
599        logging.error("".join(traceback.format_exception(etype, value, tb)))
600        msg = traceback.format_exception(etype, value, tb, limit=1)
601
602    def setTableProperties(self, table):
603        """
604        Setting table properties
605        """
606        # Table properties
607        table.verticalHeader().setVisible(False)
608        table.setAlternatingRowColors(True)
609        table.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
610        table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
611        table.resizeColumnsToContents()
612
613        # Header
614        header = table.horizontalHeader()
615        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
616
617        header.ResizeMode(QtGui.QHeaderView.Interactive)
618        header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
619        header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents)
620
621    def setPolyModel(self):
622        """
623        Set polydispersity values
624        """
625        if not self.model_parameters:
626            return
627        self._poly_model.clear()
628        for row, param in enumerate(self.model_parameters.form_volume_parameters):
629            # Counters should not be included
630            if not param.polydisperse:
631                continue
632
633            # Potential multishell params
634            checked_list = ["Distribution of "+param.name, str(param.default),
635                            str(param.limits[0]), str(param.limits[1]),
636                            "35", "3", ""]
637            FittingUtilities.addCheckedListToModel(self._poly_model, checked_list)
638
639            #TODO: Need to find cleaner way to input functions
640            func = QtGui.QComboBox()
641            func.addItems(['rectangle', 'array', 'lognormal', 'gaussian', 'schulz',])
642            func_index = self.lstPoly.model().index(row, 6)
643            self.lstPoly.setIndexWidget(func_index, func)
644
645        FittingUtilities.addPolyHeadersToModel(self._poly_model)
646
647    def setMagneticModel(self):
648        """
649        Set magnetism values on model
650        """
651        if not self.model_parameters:
652            return
653        self._magnet_model.clear()
654        for param in self.model_parameters.call_parameters:
655            if param.type != "magnetic":
656                continue
657            checked_list = [param.name,
658                            str(param.default),
659                            str(param.limits[0]),
660                            str(param.limits[1]),
661                            param.units]
662            FittingUtilities.addCheckedListToModel(self._magnet_model, checked_list)
663
664        FittingUtilities.addHeadersToModel(self._magnet_model)
665
666    def addStructureFactor(self):
667        """
668        Add structure factors to the list of parameters
669        """
670        if self.kernel_module.is_form_factor:
671            self.enableStructureCombo()
672        else:
673            self.disableStructureCombo()
674
675    def addExtraShells(self):
676        """
677        Add a combobox for multiple shell display
678        """
679        param_name, param_length = FittingUtilities.getMultiplicity(self.model_parameters)
680
681        if param_length == 0:
682            return
683
684        # cell 1: variable name
685        item1 = QtGui.QStandardItem(param_name)
686
687        func = QtGui.QComboBox()
688        func.addItems([str(i+1) for i in xrange(param_length)])
689        func.currentIndexChanged.connect(self.modifyShellsInList)
690
691        # cell 2: combobox
692        item2 = QtGui.QStandardItem()
693        self._model_model.appendRow([item1, item2])
694
695        # Beautify the row:  span columns 2-4
696        shell_row = self._model_model.rowCount()
697        shell_index = self._model_model.index(shell_row-1, 1)
698
699        self.lstParams.setIndexWidget(shell_index, func)
700        self._last_model_row = self._model_model.rowCount()
701
702    def modifyShellsInList(self, index):
703        """
704        Add/remove additional multishell parameters
705        """
706        # Find row location of the combobox
707        last_row = self._last_model_row
708        remove_rows = self._model_model.rowCount() - last_row
709
710        if remove_rows > 1:
711            self._model_model.removeRows(last_row, remove_rows)
712
713        FittingUtilities.addShellsToModel(self.model_parameters, self._model_model, index)
714
715    def togglePoly(self, isChecked):
716        """
717        Enable/disable the polydispersity tab
718        """
719        self.tabFitting.setTabEnabled(TAB_POLY, isChecked)
720
721    def toggleMagnetism(self, isChecked):
722        """
723        Enable/disable the magnetism tab
724        """
725        self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked)
726
727    def toggle2D(self, isChecked):
728        """
729        Enable/disable the controls dependent on 1D/2D data instance
730        """
731        self.chkMagnetism.setEnabled(isChecked)
732        self.is2D = isChecked
733
Note: See TracBrowser for help on using the repository browser.