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

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

Select the constrained parameter for fitting. SASVIEW-1198

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