source: sasview/src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py @ ecc5d043

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since ecc5d043 was ecc5d043, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Reworked the complex constraint functionality SASVIEW-1019

  • Property mode set to 100644
File size: 29.5 KB
Line 
1import logging
2
3from twisted.internet import threads
4
5import sas.qtgui.Utilities.GuiUtils as GuiUtils
6import sas.qtgui.Utilities.LocalConfig as LocalConfig
7
8from PyQt5 import QtGui, QtCore, QtWidgets
9
10from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
11
12import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary
13from sas.qtgui.Perspectives.Fitting.UI.ConstraintWidgetUI import Ui_ConstraintWidgetUI
14from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget
15from sas.qtgui.Perspectives.Fitting.FitThread import FitThread
16from sas.qtgui.Perspectives.Fitting.ConsoleUpdate import ConsoleUpdate
17from sas.qtgui.Perspectives.Fitting.ComplexConstraint import ComplexConstraint
18from sas.qtgui.Perspectives.Fitting.Constraint import Constraint
19
20class ConstraintWidget(QtWidgets.QWidget, Ui_ConstraintWidgetUI):
21    """
22    Constraints Dialog to select the desired parameter/model constraints.
23    """
24    fitCompleteSignal = QtCore.pyqtSignal(tuple)
25    def __init__(self, parent=None):
26        super(ConstraintWidget, self).__init__()
27        self.parent = parent
28        self.setupUi(self)
29        self.currentType = "FitPage"
30        # Page id for fitting
31        # To keep with previous SasView values, use 300 as the start offset
32        self.page_id = 301
33        self.tab_id = self.page_id
34
35        # Are we chain fitting?
36        self.is_chain_fitting = False
37
38        # Remember previous content of modified cell
39        self.current_cell = ""
40
41        # Tabs used in simultaneous fitting
42        # tab_name : True/False
43        self.tabs_for_fitting = {}
44
45        # Set up the widgets
46        self.initializeWidgets()
47
48        # Set up signals/slots
49        self.initializeSignals()
50
51        # Create the list of tabs
52        self.initializeFitList()
53
54    def acceptsData(self):
55        """ Tells the caller this widget doesn't accept data """
56        return False
57
58    def initializeWidgets(self):
59        """
60        Set up various widget states
61        """
62        labels = ['FitPage', 'Model', 'Data', 'Mnemonic']
63        # tab widget - headers
64        self.editable_tab_columns = [labels.index('Mnemonic')]
65        self.tblTabList.setColumnCount(len(labels))
66        self.tblTabList.setHorizontalHeaderLabels(labels)
67        self.tblTabList.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
68
69        self.tblTabList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
70        self.tblTabList.customContextMenuRequested.connect(self.showModelContextMenu)
71
72        # Single Fit is the default, so disable chainfit
73        self.chkChain.setVisible(False)
74
75        # disabled constraint
76        labels = ['Constraint']
77        self.tblConstraints.setColumnCount(len(labels))
78        self.tblConstraints.setHorizontalHeaderLabels(labels)
79        self.tblConstraints.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
80        self.tblConstraints.setEnabled(False)
81
82        self.tblConstraints.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
83        self.tblConstraints.customContextMenuRequested.connect(self.showConstrContextMenu)
84
85    def initializeSignals(self):
86        """
87        Set up signals/slots for this widget
88        """
89        # simple widgets
90        self.btnSingle.toggled.connect(self.onFitTypeChange)
91        self.btnBatch.toggled.connect(self.onFitTypeChange)
92        self.cbCases.currentIndexChanged.connect(self.onSpecialCaseChange)
93        self.cmdFit.clicked.connect(self.onFit)
94        self.cmdHelp.clicked.connect(self.onHelp)
95        self.cmdAdd.clicked.connect(self.showMultiConstraint)
96        self.chkChain.toggled.connect(self.onChainFit)
97
98        # QTableWidgets
99        self.tblTabList.cellChanged.connect(self.onTabCellEdit)
100        self.tblTabList.cellDoubleClicked.connect(self.onTabCellEntered)
101        self.tblConstraints.cellChanged.connect(self.onConstraintChange)
102
103        # Internal signals
104        self.fitCompleteSignal.connect(self.fitComplete)
105
106        # External signals
107        self.parent.tabsModifiedSignal.connect(self.initializeFitList)
108
109    def updateSignalsFromTab(self, tab=None):
110        """
111        Intercept update signals from fitting tabs
112        """
113        if tab is None:
114            return
115        tab_object = ObjectLibrary.getObject(tab)
116
117        # Disconnect all local slots
118        #tab_object.disconnect()
119
120        # Reconnect tab signals to local slots
121        tab_object.constraintAddedSignal.connect(self.initializeFitList)
122        tab_object.newModelSignal.connect(self.initializeFitList)
123
124    def onFitTypeChange(self, checked):
125        """
126        Respond to the fit type change
127        single fit/batch fit
128        """
129        source = self.sender().objectName()
130        self.currentType = "BatchPage" if source == "btnBatch" else "FitPage"
131        self.chkChain.setVisible(source=="btnBatch")
132        self.initializeFitList()
133
134    def onSpecialCaseChange(self, index):
135        """
136        Respond to the combobox change for special case constraint sets
137        """
138        pass
139
140    def getTabsForFit(self):
141        """
142        Returns list of tab names selected for fitting
143        """
144        return [tab for tab in self.tabs_for_fitting if self.tabs_for_fitting[tab]]
145
146    def onChainFit(self, is_checked):
147        """
148        Respond to selecting the Chain Fit checkbox
149        """
150        self.is_chain_fitting = is_checked
151
152    def onFit(self):
153        """
154        Perform the constrained/simultaneous fit
155        """
156        # Find out all tabs to fit
157        tabs_to_fit = self.getTabsForFit()
158
159        # Single fitter for the simultaneous run
160        fitter = Fit()
161        fitter.fitter_id = self.page_id
162
163        # prepare fitting problems for each tab
164        #
165        page_ids = []
166        fitter_id = 0
167        sim_fitter_list=[fitter]
168        # Prepare the fitter object
169        try:
170            for tab in tabs_to_fit:
171                tab_object = ObjectLibrary.getObject(tab)
172                if tab_object is None:
173                    # No such tab!
174                    return
175                sim_fitter_list, fitter_id = \
176                    tab_object.prepareFitters(fitter=sim_fitter_list[0], fit_id=fitter_id)
177                page_ids.append([tab_object.page_id])
178        except ValueError:
179            # No parameters selected in one of the tabs
180            no_params_msg = "Fitting cannot be performed.\n" +\
181                            "Not all tabs chosen for fitting have parameters selected for fitting."
182            QtWidgets.QMessageBox.warning(self,
183                                          'Warning',
184                                           no_params_msg,
185                                           QtWidgets.QMessageBox.Ok)
186
187            return
188
189        # Create the fitting thread, based on the fitter
190        completefn = self.onBatchFitComplete if self.currentType=='BatchPage' else self.onFitComplete
191
192        if LocalConfig.USING_TWISTED:
193            handler = None
194            updater = None
195        else:
196            handler = ConsoleUpdate(parent=self.parent,
197                                    manager=self,
198                                    improvement_delta=0.1)
199            updater = handler.update_fit
200
201        batch_inputs = {}
202        batch_outputs = {}
203
204        # Notify the parent about fitting started
205        self.parent.fittingStartedSignal.emit(tabs_to_fit)
206
207        # new fit thread object
208        calc_fit = FitThread(handler=handler,
209                             fn=sim_fitter_list,
210                             batch_inputs=batch_inputs,
211                             batch_outputs=batch_outputs,
212                             page_id=page_ids,
213                             updatefn=updater,
214                             completefn=completefn,
215                             reset_flag=self.is_chain_fitting)
216
217        if LocalConfig.USING_TWISTED:
218            # start the trhrhread with twisted
219            calc_thread = threads.deferToThread(calc_fit.compute)
220            calc_thread.addCallback(completefn)
221            calc_thread.addErrback(self.onFitFailed)
222        else:
223            # Use the old python threads + Queue
224            calc_fit.queue()
225            calc_fit.ready(2.5)
226
227
228        #disable the Fit button
229        self.cmdFit.setText('Running...')
230        self.parent.communicate.statusBarUpdateSignal.emit('Fitting started...')
231        self.cmdFit.setEnabled(False)
232
233    def onHelp(self):
234        """
235        Show the "Fitting" section of help
236        """
237        tree_location = "/user/qtgui/Perspectives/Fitting/"
238
239        helpfile = "fitting_help.html#simultaneous-fit-mode"
240        help_location = tree_location + helpfile
241
242        # OMG, really? Crawling up the object hierarchy...
243        self.parent.parent.showHelp(help_location)
244
245    def onTabCellEdit(self, row, column):
246        """
247        Respond to check/uncheck and to modify the model moniker actions
248        """
249        item = self.tblTabList.item(row, column)
250        if column == 0:
251            # Update the tabs for fitting list
252            tab_name = item.text()
253            self.tabs_for_fitting[tab_name] = (item.checkState() == QtCore.Qt.Checked)
254            # Enable fitting only when there are models to fit
255            self.cmdFit.setEnabled(any(self.tabs_for_fitting.values()))
256
257        if column not in self.editable_tab_columns:
258            return
259        new_moniker = item.data(0)
260
261        # The new name should be validated on the fly, with QValidator
262        # but let's just assure it post-factum
263        is_good_moniker = self.validateMoniker(new_moniker)
264        if not is_good_moniker:
265            self.tblTabList.blockSignals(True)
266            item.setBackground(QtCore.Qt.red)
267            self.tblTabList.blockSignals(False)
268            self.cmdFit.setEnabled(False)
269            return
270        self.tblTabList.blockSignals(True)
271        item.setBackground(QtCore.Qt.white)
272        self.tblTabList.blockSignals(False)
273        self.cmdFit.setEnabled(True)
274        if not self.current_cell:
275            return
276        # Remember the value
277        if self.current_cell not in self.available_tabs:
278            return
279        temp_tab = self.available_tabs[self.current_cell]
280        # Remove the key from the dictionaries
281        self.available_tabs.pop(self.current_cell, None)
282        # Change the model name
283        model = temp_tab.kernel_module
284        model.name = new_moniker
285        # Replace constraint name
286        temp_tab.replaceConstraintName(self.current_cell, new_moniker)
287        # Replace constraint name in the remaining tabs
288        for tab in self.available_tabs.values():
289            tab.replaceConstraintName(self.current_cell, new_moniker)
290        # Reinitialize the display
291        self.initializeFitList()
292
293    def onConstraintChange(self, row, column):
294        """
295        Modify the constraint's "active" instance variable.
296        """
297        item = self.tblConstraints.item(row, column)
298        if column != 0: return
299        # Update the tabs for fitting list
300        constraint = self.available_constraints[row]
301        constraint.active = (item.checkState() == QtCore.Qt.Checked)
302        # Update the constraint formula
303        constraint = self.available_constraints[row]
304        function = item.text()
305        # remove anything left of '=' to get the constraint
306        function = function[function.index('=')+1:]
307        # No check on function here - trust the user (R)
308        if function != constraint.func:
309            #from sas.sascalc.fit.expression import compile_constraints
310            constraint.func = function
311
312    def onTabCellEntered(self, row, column):
313        """
314        Remember the original tab list cell data.
315        Needed for reverting back on bad validation
316        """
317        if column != 3:
318            return
319        self.current_cell = self.tblTabList.item(row, column).data(0)
320
321    def onFitComplete(self, result):
322        """
323        Send the fit complete signal to main thread
324        """
325        self.fitCompleteSignal.emit(result)
326
327    def fitComplete(self, result):
328        """
329        Respond to the successful fit complete signal
330        """
331        #re-enable the Fit button
332        self.cmdFit.setText("Fit")
333        self.cmdFit.setEnabled(True)
334
335        # Notify the parent about completed fitting
336        self.parent.fittingStoppedSignal.emit(self.getTabsForFit())
337
338        # Assure the fitting succeeded
339        if result is None or not result:
340            msg = "Fitting failed. Please ensure correctness of chosen constraints."
341            self.parent.communicate.statusBarUpdateSignal.emit(msg)
342            return
343
344        # get the elapsed time
345        elapsed = result[1]
346
347        # result list
348        results = result[0][0]
349
350        # Find out all tabs to fit
351        tabs_to_fit = [tab for tab in self.tabs_for_fitting if self.tabs_for_fitting[tab]]
352
353        # update all involved tabs
354        for i, tab in enumerate(tabs_to_fit):
355            tab_object = ObjectLibrary.getObject(tab)
356            if tab_object is None:
357                # No such tab. removed while job was running
358                return
359            # Make sure result and target objects are the same (same model moniker)
360            if tab_object.kernel_module.name == results[i].model.name:
361                tab_object.fitComplete(([[results[i]]], elapsed))
362
363        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed)
364        self.parent.communicate.statusBarUpdateSignal.emit(msg)
365
366    def onBatchFitComplete(self, result):
367        """
368        Respond to the successful batch fit complete signal
369        """
370        #re-enable the Fit button
371        self.cmdFit.setText("Fit")
372        self.cmdFit.setEnabled(True)
373
374        # Notify the parent about completed fitting
375        self.parent.fittingStoppedSignal.emit(self.getTabsForFit())
376
377        # get the elapsed time
378        elapsed = result[1]
379
380        if result is None:
381            msg = "Fitting failed."
382            self.parent.communicate.statusBarUpdateSignal.emit(msg)
383            return
384
385        # Show the grid panel
386        self.parent.communicate.sendDataToGridSignal.emit(result[0])
387
388        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed)
389        self.parent.communicate.statusBarUpdateSignal.emit(msg)
390
391    def onFitFailed(self, reason):
392        """
393        Respond to fitting failure.
394        """
395        #re-enable the Fit button
396        self.cmdFit.setText("Fit")
397        self.cmdFit.setEnabled(True)
398
399        # Notify the parent about completed fitting
400        self.parent.fittingStoppedSignal.emit(self.getTabsForFit())
401
402        msg = "Fitting failed: %s s.\n" % reason
403        self.parent.communicate.statusBarUpdateSignal.emit(msg)
404
405    def isTabImportable(self, tab):
406        """
407        Determines if the tab can be imported and included in the widget
408        """
409        if not isinstance(tab, str): return False
410        if not self.currentType in tab: return False
411        object = ObjectLibrary.getObject(tab)
412        if not isinstance(object, FittingWidget): return False
413        if not object.data_is_loaded : return False
414        return True
415
416    def showModelContextMenu(self, position):
417        """
418        Show context specific menu in the tab table widget.
419        """
420        menu = QtWidgets.QMenu()
421        rows = [s.row() for s in self.tblTabList.selectionModel().selectedRows()]
422        num_rows = len(rows)
423        if num_rows <= 0:
424            return
425        # Select for fitting
426        param_string = "Fit Page " if num_rows==1 else "Fit Pages "
427
428        self.actionSelect = QtWidgets.QAction(self)
429        self.actionSelect.setObjectName("actionSelect")
430        self.actionSelect.setText(QtCore.QCoreApplication.translate("self", "Select "+param_string+" for fitting"))
431        # Unselect from fitting
432        self.actionDeselect = QtWidgets.QAction(self)
433        self.actionDeselect.setObjectName("actionDeselect")
434        self.actionDeselect.setText(QtCore.QCoreApplication.translate("self", "De-select "+param_string+" from fitting"))
435
436        self.actionRemoveConstraint = QtWidgets.QAction(self)
437        self.actionRemoveConstraint.setObjectName("actionRemoveConstrain")
438        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove all constraints on selected models"))
439
440        self.actionMutualMultiConstrain = QtWidgets.QAction(self)
441        self.actionMutualMultiConstrain.setObjectName("actionMutualMultiConstrain")
442        self.actionMutualMultiConstrain.setText(QtCore.QCoreApplication.translate("self", "Mutual constrain of parameters in selected models..."))
443
444        menu.addAction(self.actionSelect)
445        menu.addAction(self.actionDeselect)
446        menu.addSeparator()
447
448        if num_rows >= 2:
449            menu.addAction(self.actionMutualMultiConstrain)
450
451        # Define the callbacks
452        self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint)
453        self.actionSelect.triggered.connect(self.selectModels)
454        self.actionDeselect.triggered.connect(self.deselectModels)
455        try:
456            menu.exec_(self.tblTabList.viewport().mapToGlobal(position))
457        except AttributeError as ex:
458            logging.error("Error generating context menu: %s" % ex)
459        return
460
461    def showConstrContextMenu(self, position):
462        """
463        Show context specific menu in the tab table widget.
464        """
465        menu = QtWidgets.QMenu()
466        rows = [s.row() for s in self.tblConstraints.selectionModel().selectedRows()]
467        num_rows = len(rows)
468        if num_rows <= 0:
469            return
470        # Select for fitting
471        param_string = "constraint " if num_rows==1 else "constraints "
472
473        self.actionSelect = QtWidgets.QAction(self)
474        self.actionSelect.setObjectName("actionSelect")
475        self.actionSelect.setText(QtCore.QCoreApplication.translate("self", "Select "+param_string+" for fitting"))
476        # Unselect from fitting
477        self.actionDeselect = QtWidgets.QAction(self)
478        self.actionDeselect.setObjectName("actionDeselect")
479        self.actionDeselect.setText(QtCore.QCoreApplication.translate("self", "De-select "+param_string+" from fitting"))
480
481        self.actionRemoveConstraint = QtWidgets.QAction(self)
482        self.actionRemoveConstraint.setObjectName("actionRemoveConstrain")
483        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove "+param_string))
484
485        menu.addAction(self.actionSelect)
486        menu.addAction(self.actionDeselect)
487        menu.addSeparator()
488        menu.addAction(self.actionRemoveConstraint)
489
490        # Define the callbacks
491        self.actionRemoveConstraint.triggered.connect(self.deleteConstraint)
492        self.actionSelect.triggered.connect(self.selectConstraints)
493        self.actionDeselect.triggered.connect(self.deselectConstraints)
494        try:
495            menu.exec_(self.tblConstraints.viewport().mapToGlobal(position))
496        except AttributeError as ex:
497            logging.error("Error generating context menu: %s" % ex)
498        return
499
500    def selectConstraints(self):
501        """
502        Selected constraints are chosen for fitting
503        """
504        status = QtCore.Qt.Checked
505        self.setRowSelection(self.tblConstraints, status)
506
507    def deselectConstraints(self):
508        """
509        Selected constraints are removed for fitting
510        """
511        status = QtCore.Qt.Unchecked
512        self.setRowSelection(self.tblConstraints, status)
513
514    def selectModels(self):
515        """
516        Selected models are chosen for fitting
517        """
518        status = QtCore.Qt.Checked
519        self.setRowSelection(self.tblTabList, status)
520
521    def deselectModels(self):
522        """
523        Selected models are removed for fitting
524        """
525        status = QtCore.Qt.Unchecked
526        self.setRowSelection(self.tblTabList, status)
527
528    def selectedParameters(self, widget):
529        """ Returns list of selected (highlighted) parameters """
530        return [s.row() for s in widget.selectionModel().selectedRows()]
531
532    def setRowSelection(self, widget, status=QtCore.Qt.Unchecked):
533        """
534        Selected models are chosen for fitting
535        """
536        # Convert to proper indices and set requested enablement
537        for row in self.selectedParameters(widget):
538            widget.item(row, 0).setCheckState(status)
539
540    def deleteConstraint(self):#, row):
541        """
542        Delete all selected constraints.
543        """
544        # Removing rows from the table we're iterating over,
545        # so prepare a list of data first
546        constraints_to_delete = []
547        for row in self.selectedParameters(self.tblConstraints):
548            constraints_to_delete.append(self.tblConstraints.item(row, 0).data(0))
549        for constraint in constraints_to_delete:
550            moniker = constraint[:constraint.index(':')]
551            param = constraint[constraint.index(':')+1:constraint.index('=')].strip()
552            tab = self.available_tabs[moniker]
553            tab.deleteConstraintOnParameter(param)
554        # Constraints removed - refresh the table widget
555        self.initializeFitList()
556
557    def uneditableItem(self, data=""):
558        """
559        Returns an uneditable Table Widget Item
560        """
561        item = QtWidgets.QTableWidgetItem(data)
562        item.setFlags( QtCore.Qt.ItemIsSelectable |  QtCore.Qt.ItemIsEnabled )
563        return item
564
565    def updateFitLine(self, tab):
566        """
567        Update a single line of the table widget with tab info
568        """
569        fit_page = ObjectLibrary.getObject(tab)
570        model = fit_page.kernel_module
571        if model is None:
572            return
573        tab_name = tab
574        model_name = model.id
575        moniker = model.name
576        model_data = fit_page.data
577        model_filename = model_data.filename
578        self.available_tabs[moniker] = fit_page
579
580        # Update the model table widget
581        pos = self.tblTabList.rowCount()
582        self.tblTabList.insertRow(pos)
583        item = self.uneditableItem(tab_name)
584        item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable)
585        if tab_name in self.tabs_for_fitting:
586            state = QtCore.Qt.Checked if self.tabs_for_fitting[tab_name] else QtCore.Qt.Unchecked
587            item.setCheckState(state)
588        else:
589            item.setCheckState(QtCore.Qt.Checked)
590            self.tabs_for_fitting[tab_name] = True
591
592        # Disable signals so we don't get infinite call recursion
593        self.tblTabList.blockSignals(True)
594        self.tblTabList.setItem(pos, 0, item)
595        self.tblTabList.setItem(pos, 1, self.uneditableItem(model_name))
596        self.tblTabList.setItem(pos, 2, self.uneditableItem(model_filename))
597        # Moniker is editable, so no option change
598        item = QtWidgets.QTableWidgetItem(moniker)
599        self.tblTabList.setItem(pos, 3, item)
600        self.tblTabList.blockSignals(False)
601
602        # Check if any constraints present in tab
603        constraint_names = fit_page.getComplexConstraintsForModel()
604        constraints = fit_page.getConstraintObjectsForModel()
605        if not constraints: 
606            return
607        self.tblConstraints.setEnabled(True)
608        self.tblConstraints.blockSignals(True)
609        for constraint, constraint_name in zip(constraints, constraint_names):
610            # Create the text for widget item
611            label = moniker + ":"+ constraint_name[0] + " = " + constraint_name[1]
612            pos = self.tblConstraints.rowCount()
613            self.available_constraints[pos] = constraint
614
615            # Show the text in the constraint table
616            item = self.uneditableItem(label)
617            item = QtWidgets.QTableWidgetItem(label)
618            item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable)
619            item.setCheckState(QtCore.Qt.Checked)
620            self.tblConstraints.insertRow(pos)
621            self.tblConstraints.setItem(pos, 0, item)
622        self.tblConstraints.blockSignals(False)
623
624    def initializeFitList(self):
625        """
626        Fill the list of model/data sets for fitting/constraining
627        """
628        # look at the object library to find all fit tabs
629        # Show the content of the current "model"
630        objects = ObjectLibrary.listObjects()
631
632        # Tab dict
633        # moniker -> (kernel_module, data)
634        self.available_tabs = {}
635        # Constraint dict
636        # moniker -> [constraints]
637        self.available_constraints = {}
638
639        # Reset the table widgets
640        self.tblTabList.setRowCount(0)
641        self.tblConstraints.setRowCount(0)
642
643        # Fit disabled
644        self.cmdFit.setEnabled(False)
645
646        if not objects:
647            return
648
649        tabs = [tab for tab in ObjectLibrary.listObjects() if self.isTabImportable(tab)]
650        for tab in tabs:
651            self.updateFitLine(tab)
652            self.updateSignalsFromTab(tab)
653            # We have at least 1 fit page, allow fitting
654            self.cmdFit.setEnabled(True)
655
656    def validateMoniker(self, new_moniker=None):
657        """
658        Check new_moniker for correctness.
659        It must be non-empty.
660        It must not be the same as other monikers.
661        """
662        if not new_moniker:
663            return False
664
665        for existing_moniker in self.available_tabs:
666            if existing_moniker == new_moniker and existing_moniker != self.current_cell:
667                return False
668
669        return True
670
671    def getObjectByName(self, name):
672        """
673        Given name of the fit, returns associated fit object
674        """
675        for object_name in ObjectLibrary.listObjects():
676            object = ObjectLibrary.getObject(object_name)
677            if isinstance(object, FittingWidget):
678                try:
679                    if object.kernel_module.name == name:
680                        return object
681                except AttributeError:
682                    # Disregard atribute errors - empty fit widgets
683                    continue
684        return None
685
686    def onAcceptConstraint(self, con_tuple):
687        """
688        Receive constraint tuple from the ComplexConstraint dialog and adds contraint
689        """
690        #"M1, M2, M3" etc
691        model_name, constraint = con_tuple
692        constrained_tab = self.getObjectByName(model_name)
693        if constrained_tab is None:
694            return
695
696        # Find the constrained parameter row
697        constrained_row = constrained_tab.getRowFromName(constraint.param)
698
699        # Update the tab
700        constrained_tab.addConstraintToRow(constraint, constrained_row)
701
702    def showMultiConstraint(self):
703        """
704        Invoke the complex constraint editor
705        """
706        selected_rows = self.selectedParameters(self.tblTabList)
707        if len(selected_rows)!=2:
708            msg = "Please select two fit pages from the Source Choice table."
709            msgbox = QtWidgets.QMessageBox(self.parent)
710            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
711            msgbox.setText(msg)
712            msgbox.setWindowTitle("2 fit page constraints")
713            retval = msgbox.exec_()
714            return
715
716        tab_list = [ObjectLibrary.getObject(self.tblTabList.item(s, 0).data(0)) for s in selected_rows]
717        # Create and display the widget for param1 and param2
718        cc_widget = ComplexConstraint(self, tabs=tab_list)
719        cc_widget.constraintReadySignal.connect(self.onAcceptConstraint)
720
721        if cc_widget.exec_() != QtWidgets.QDialog.Accepted:
722            return
723
724    def getFitPage(self):
725        """
726        Retrieves the state of this page
727        """
728        param_list = []
729
730        param_list.append(['is_constraint', 'True'])
731        param_list.append(['data_id', "cs_tab"+str(self.page_id)])
732        param_list.append(['current_type', self.currentType])
733        param_list.append(['is_chain_fitting', str(self.is_chain_fitting)])
734        param_list.append(['special_case', self.cbCases.currentText()])
735
736        return param_list
737
738    def getFitModel(self):
739        """
740        Retrieves current model
741        """
742        model_list = []
743
744        checked_models = {}
745        for row in range(self.tblTabList.rowCount()):
746            model_name = self.tblTabList.item(row,1).data(0)
747            active = self.tblTabList.item(row,0).checkState()# == QtCore.Qt.Checked
748            checked_models[model_name] = str(active)
749
750        checked_constraints = {}
751        for row in range(self.tblConstraints.rowCount()):
752            model_name = self.tblConstraints.item(row,0).data(0)
753            active = self.tblConstraints.item(row,0).checkState()# == QtCore.Qt.Checked
754            checked_constraints[model_name] = str(active)
755
756        model_list.append(['checked_models', checked_models])
757        model_list.append(['checked_constraints', checked_constraints])
758        return model_list
759
760    def createPageForParameters(self, parameters=None):
761        """
762        Update the page with passed parameter values
763        """
764        # checked models
765        if not 'checked_models' in parameters:
766            return
767        models = parameters['checked_models'][0]
768        for model, check_state in models.items():
769            for row in range(self.tblTabList.rowCount()):
770                model_name = self.tblTabList.item(row,1).data(0)
771                if model_name != model:
772                    continue
773                # check/uncheck item
774                self.tblTabList.item(row,0).setCheckState(int(check_state))
775
776        if not 'checked_constraints' in parameters:
777            return
778        # checked constraints
779        models = parameters['checked_constraints'][0]
780        for model, check_state in models.items():
781            for row in range(self.tblConstraints.rowCount()):
782                model_name = self.tblConstraints.item(row,0).data(0)
783                if model_name != model:
784                    continue
785                # check/uncheck item
786                self.tblConstraints.item(row,0).setCheckState(int(check_state))
787
788        # fit/batch radio
789        isBatch = parameters['current_type'][0] == 'BatchPage'
790        if isBatch:
791            self.btnBatch.toggle()
792
793        # chain
794        is_chain = parameters['is_chain_fitting'][0] == 'True'
795        if isBatch:
796            self.chkChain.setChecked(is_chain)
797
798
Note: See TracBrowser for help on using the repository browser.