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

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

Merge branch 'ESS_GUI_project_save' into ESS_GUI

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