source: sasview/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py @ d7adf97

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since d7adf97 was d7adf97, checked in by wojciech, 6 years ago

Merge branch 'ESS_GUI' of https://github.com/SasView/sasview into ESS_GUI_Pr_fixes

  • Property mode set to 100644
File size: 35.1 KB
RevLine 
[fa81e94]1import logging
2import numpy as np
3
4from PyQt5 import QtGui, QtCore, QtWidgets
5
6# sas-global
7import sas.qtgui.Utilities.GuiUtils as GuiUtils
8
9# pr inversion GUI elements
10from .InversionUtils import WIDGETS
11from .UI.TabbedInversionUI import Ui_PrInversion
12from .InversionLogic import InversionLogic
13
14# pr inversion calculation elements
15from sas.sascalc.pr.invertor import Invertor
[855e7ad]16from sas.qtgui.Plotting.PlotterData import Data1D
[effdd98]17# Batch calculation display
18from sas.qtgui.Utilities.GridPanel import BatchInversionOutputPanel
19
[b9e89d5]20
[fa81e94]21def is_float(value):
22    """Converts text input values to floats. Empty strings throw ValueError"""
23    try:
24        return float(value)
25    except ValueError:
26        return 0.0
27
[b9e89d5]28
[50bfab0]29NUMBER_OF_TERMS = 10
30REGULARIZATION = 0.0001
31BACKGROUND_INPUT = 0.0
32MAX_DIST = 140.0
[edd6720]33DICT_KEYS = ["Calculator", "PrPlot", "DataPlot"]
[47bf906]34
[6da860a]35logger = logging.getLogger(__name__)
36
[fa81e94]37
[d4881f6a]38class InversionWindow(QtWidgets.QDialog, Ui_PrInversion):
[fa81e94]39    """
40    The main window for the P(r) Inversion perspective.
41    """
42
43    name = "Inversion"
[f1ec901]44    estimateSignal = QtCore.pyqtSignal(tuple)
45    estimateNTSignal = QtCore.pyqtSignal(tuple)
[34cf92c]46    estimateDynamicNTSignal = QtCore.pyqtSignal(tuple)
47    estimateDynamicSignal = QtCore.pyqtSignal(tuple)
[f1ec901]48    calculateSignal = QtCore.pyqtSignal(tuple)
[fa81e94]49
50    def __init__(self, parent=None, data=None):
51        super(InversionWindow, self).__init__()
52        self.setupUi(self)
53
54        self.setWindowTitle("P(r) Inversion Perspective")
55
56        self._manager = parent
[4fbf0db]57        self._parent = parent
[b9e89d5]58        self.communicate = parent.communicator()
59        self.communicate.dataDeletedSignal.connect(self.removeData)
[fa81e94]60
61        self.logic = InversionLogic()
62
63        # The window should not close
[ae34d30]64        self._allowClose = False
[fa81e94]65
[ae34d30]66        # Visible data items
[fa81e94]67        # current QStandardItem showing on the panel
68        self._data = None
[47bf906]69        # Reference to Dmax window for self._data
70        self.dmaxWindow = None
71        # p(r) calculator for self._data
[fa81e94]72        self._calculator = Invertor()
[304e42f]73        # Default to background estimate
[bb6b037]74        self._calculator.est_bck = True
[47bf906]75        # plots of self._data
[ae34d30]76        self.prPlot = None
77        self.dataPlot = None
[e51e078]78        # suggested nTerms
79        self.nTermsSuggested = NUMBER_OF_TERMS
[47bf906]80
81        # Calculation threads used by all data items
[ae34d30]82        self.calcThread = None
83        self.estimationThread = None
84        self.estimationThreadNT = None
[72ecbdf2]85        self.isCalculating = False
[fa81e94]86
[47bf906]87        # Mapping for all data items
[ae34d30]88        # Dictionary mapping data to all parameters
89        self._dataList = {}
[fa81e94]90        if not isinstance(data, list):
91            data_list = [data]
92        if data is not None:
93            for datum in data_list:
[e51e078]94                self.updateDataList(datum)
[f1ec901]95
[044454d]96        self.dataDeleted = False
97
[fa81e94]98        self.model = QtGui.QStandardItemModel(self)
99        self.mapper = QtWidgets.QDataWidgetMapper(self)
[8f83719f]100
[ae34d30]101        # Batch fitting parameters
102        self.isBatch = False
103        self.batchResultsWindow = None
[98485fe]104        self.batchResults = {}
105        self.batchComplete = []
[effdd98]106
[8f83719f]107        # Add validators
108        self.setupValidators()
[fa81e94]109        # Link user interactions with methods
110        self.setupLinks()
111        # Set values
112        self.setupModel()
113        # Set up the Widget Map
114        self.setupMapper()
[68dc2873]115
116        #Hidding calculate all buton
117        self.calculateAllButton.setVisible(False)
[fa81e94]118        # Set base window state
119        self.setupWindow()
120
121    ######################################################################
122    # Base Perspective Class Definitions
123
124    def communicator(self):
125        return self.communicate
126
127    def allowBatch(self):
[68dc2873]128        return False
[fa81e94]129
130    def setClosable(self, value=True):
131        """
132        Allow outsiders close this widget
133        """
134        assert isinstance(value, bool)
[ae34d30]135        self._allowClose = value
[fa81e94]136
[917eba5]137    def isClosable(self):
138        """
139        Allow outsiders close this widget
140        """
141        return self._allowClose
142
[fa81e94]143    def closeEvent(self, event):
144        """
145        Overwrite QDialog close method to allow for custom widget close
146        """
[ae34d30]147        # Close report widgets before closing/minimizing main widget
148        self.closeDMax()
149        self.closeBatchResults()
150        if self._allowClose:
[fa81e94]151            # reset the closability flag
152            self.setClosable(value=False)
[d4881f6a]153            # Tell the MdiArea to close the container
154            self.parentWidget().close()
[fa81e94]155            event.accept()
156        else:
157            event.ignore()
158            # Maybe we should just minimize
159            self.setWindowState(QtCore.Qt.WindowMinimized)
160
[ae34d30]161    def closeDMax(self):
162        if self.dmaxWindow is not None:
163            self.dmaxWindow.close()
164
165    def closeBatchResults(self):
166        if self.batchResultsWindow is not None:
167            self.batchResultsWindow.close()
168
[fa81e94]169    ######################################################################
170    # Initialization routines
171
172    def setupLinks(self):
173        """Connect the use controls to their appropriate methods"""
174        self.dataList.currentIndexChanged.connect(self.displayChange)
[f1ec901]175        self.calculateAllButton.clicked.connect(self.startThreadAll)
176        self.calculateThisButton.clicked.connect(self.startThread)
[72ecbdf2]177        self.stopButton.clicked.connect(self.stopCalculation)
[fa81e94]178        self.removeButton.clicked.connect(self.removeData)
179        self.helpButton.clicked.connect(self.help)
180        self.estimateBgd.toggled.connect(self.toggleBgd)
181        self.manualBgd.toggled.connect(self.toggleBgd)
182        self.regConstantSuggestionButton.clicked.connect(self.acceptAlpha)
183        self.noOfTermsSuggestionButton.clicked.connect(self.acceptNoTerms)
184        self.explorerButton.clicked.connect(self.openExplorerWindow)
[f1ec901]185
[d79bb7e]186        self.backgroundInput.textChanged.connect(
[bb6b037]187            lambda: self.set_background(self.backgroundInput.text()))
[d79bb7e]188        self.minQInput.textChanged.connect(
[8f83719f]189            lambda: self._calculator.set_qmin(is_float(self.minQInput.text())))
[d79bb7e]190        self.regularizationConstantInput.textChanged.connect(
[8f83719f]191            lambda: self._calculator.set_alpha(is_float(self.regularizationConstantInput.text())))
[d79bb7e]192        self.maxDistanceInput.textChanged.connect(
[8f83719f]193            lambda: self._calculator.set_dmax(is_float(self.maxDistanceInput.text())))
[d79bb7e]194        self.maxQInput.textChanged.connect(
[8f83719f]195            lambda: self._calculator.set_qmax(is_float(self.maxQInput.text())))
[d79bb7e]196        self.slitHeightInput.textChanged.connect(
[8f83719f]197            lambda: self._calculator.set_slit_height(is_float(self.slitHeightInput.text())))
[d79bb7e]198        self.slitWidthInput.textChanged.connect(
199            lambda: self._calculator.set_slit_width(is_float(self.slitWidthInput.text())))
[f1ec901]200
[fa81e94]201        self.model.itemChanged.connect(self.model_changed)
[f1ec901]202        self.estimateNTSignal.connect(self._estimateNTUpdate)
[34cf92c]203        self.estimateDynamicNTSignal.connect(self._estimateDynamicNTUpdate)
204        self.estimateDynamicSignal.connect(self._estimateDynamicUpdate)
[f1ec901]205        self.estimateSignal.connect(self._estimateUpdate)
206        self.calculateSignal.connect(self._calculateUpdate)
[fa81e94]207
[34cf92c]208        self.maxDistanceInput.textEdited.connect(self.performEstimateDynamic)
209
[fa81e94]210    def setupMapper(self):
211        # Set up the mapper.
212        self.mapper.setOrientation(QtCore.Qt.Vertical)
213        self.mapper.setModel(self.model)
214
215        # Filename
216        self.mapper.addMapping(self.dataList, WIDGETS.W_FILENAME)
217        # Background
218        self.mapper.addMapping(self.backgroundInput, WIDGETS.W_BACKGROUND_INPUT)
219        self.mapper.addMapping(self.estimateBgd, WIDGETS.W_ESTIMATE)
220        self.mapper.addMapping(self.manualBgd, WIDGETS.W_MANUAL_INPUT)
221
222        # Qmin/Qmax
223        self.mapper.addMapping(self.minQInput, WIDGETS.W_QMIN)
224        self.mapper.addMapping(self.maxQInput, WIDGETS.W_QMAX)
225
226        # Slit Parameter items
227        self.mapper.addMapping(self.slitWidthInput, WIDGETS.W_SLIT_WIDTH)
228        self.mapper.addMapping(self.slitHeightInput, WIDGETS.W_SLIT_HEIGHT)
229
230        # Parameter Items
[8f83719f]231        self.mapper.addMapping(self.regularizationConstantInput, WIDGETS.W_REGULARIZATION)
232        self.mapper.addMapping(self.regConstantSuggestionButton, WIDGETS.W_REGULARIZATION_SUGGEST)
[fa81e94]233        self.mapper.addMapping(self.explorerButton, WIDGETS.W_EXPLORE)
234        self.mapper.addMapping(self.maxDistanceInput, WIDGETS.W_MAX_DIST)
235        self.mapper.addMapping(self.noOfTermsInput, WIDGETS.W_NO_TERMS)
[8f83719f]236        self.mapper.addMapping(self.noOfTermsSuggestionButton, WIDGETS.W_NO_TERMS_SUGGEST)
[fa81e94]237
238        # Output
239        self.mapper.addMapping(self.rgValue, WIDGETS.W_RG)
240        self.mapper.addMapping(self.iQ0Value, WIDGETS.W_I_ZERO)
241        self.mapper.addMapping(self.backgroundValue, WIDGETS.W_BACKGROUND_OUTPUT)
242        self.mapper.addMapping(self.computationTimeValue, WIDGETS.W_COMP_TIME)
243        self.mapper.addMapping(self.chiDofValue, WIDGETS.W_CHI_SQUARED)
244        self.mapper.addMapping(self.oscillationValue, WIDGETS.W_OSCILLATION)
245        self.mapper.addMapping(self.posFractionValue, WIDGETS.W_POS_FRACTION)
[8f83719f]246        self.mapper.addMapping(self.sigmaPosFractionValue, WIDGETS.W_SIGMA_POS_FRACTION)
[fa81e94]247
248        # Main Buttons
249        self.mapper.addMapping(self.removeButton, WIDGETS.W_REMOVE)
[f1ec901]250        self.mapper.addMapping(self.calculateAllButton, WIDGETS.W_CALCULATE_ALL)
[8f83719f]251        self.mapper.addMapping(self.calculateThisButton, WIDGETS.W_CALCULATE_VISIBLE)
[fa81e94]252        self.mapper.addMapping(self.helpButton, WIDGETS.W_HELP)
253
254        self.mapper.toFirst()
255
256    def setupModel(self):
257        """
258        Update boxes with initial values
259        """
[ae34d30]260        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT))
261        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, bgd_item)
[d79bb7e]262        blank_item = QtGui.QStandardItem("")
[ae34d30]263        self.model.setItem(WIDGETS.W_QMIN, blank_item)
[d79bb7e]264        blank_item = QtGui.QStandardItem("")
[ae34d30]265        self.model.setItem(WIDGETS.W_QMAX, blank_item)
[d79bb7e]266        blank_item = QtGui.QStandardItem("")
[ae34d30]267        self.model.setItem(WIDGETS.W_SLIT_WIDTH, blank_item)
[d79bb7e]268        blank_item = QtGui.QStandardItem("")
[ae34d30]269        self.model.setItem(WIDGETS.W_SLIT_HEIGHT, blank_item)
[d79bb7e]270        no_terms_item = QtGui.QStandardItem(str(NUMBER_OF_TERMS))
[ae34d30]271        self.model.setItem(WIDGETS.W_NO_TERMS, no_terms_item)
[d79bb7e]272        reg_item = QtGui.QStandardItem(str(REGULARIZATION))
[ae34d30]273        self.model.setItem(WIDGETS.W_REGULARIZATION, reg_item)
[d79bb7e]274        max_dist_item = QtGui.QStandardItem(str(MAX_DIST))
[ae34d30]275        self.model.setItem(WIDGETS.W_MAX_DIST, max_dist_item)
[d79bb7e]276        blank_item = QtGui.QStandardItem("")
[ae34d30]277        self.model.setItem(WIDGETS.W_RG, blank_item)
[d79bb7e]278        blank_item = QtGui.QStandardItem("")
[ae34d30]279        self.model.setItem(WIDGETS.W_I_ZERO, blank_item)
[d79bb7e]280        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT))
[ae34d30]281        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, bgd_item)
[d79bb7e]282        blank_item = QtGui.QStandardItem("")
[ae34d30]283        self.model.setItem(WIDGETS.W_COMP_TIME, blank_item)
[d79bb7e]284        blank_item = QtGui.QStandardItem("")
[ae34d30]285        self.model.setItem(WIDGETS.W_CHI_SQUARED, blank_item)
[d79bb7e]286        blank_item = QtGui.QStandardItem("")
[ae34d30]287        self.model.setItem(WIDGETS.W_OSCILLATION, blank_item)
[d79bb7e]288        blank_item = QtGui.QStandardItem("")
[ae34d30]289        self.model.setItem(WIDGETS.W_POS_FRACTION, blank_item)
[d79bb7e]290        blank_item = QtGui.QStandardItem("")
[ae34d30]291        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, blank_item)
[fa81e94]292
293    def setupWindow(self):
294        """Initialize base window state on init"""
295        self.enableButtons()
296        self.estimateBgd.setChecked(True)
297
[8f83719f]298    def setupValidators(self):
299        """Apply validators to editable line edits"""
300        self.noOfTermsInput.setValidator(QtGui.QIntValidator())
301        self.regularizationConstantInput.setValidator(GuiUtils.DoubleValidator())
302        self.maxDistanceInput.setValidator(GuiUtils.DoubleValidator())
303        self.minQInput.setValidator(GuiUtils.DoubleValidator())
304        self.maxQInput.setValidator(GuiUtils.DoubleValidator())
305        self.slitHeightInput.setValidator(GuiUtils.DoubleValidator())
306        self.slitWidthInput.setValidator(GuiUtils.DoubleValidator())
307
[fa81e94]308    ######################################################################
309    # Methods for updating GUI
310
311    def enableButtons(self):
312        """
313        Enable buttons when data is present, else disable them
314        """
[ae34d30]315        self.calculateAllButton.setEnabled(len(self._dataList) > 1
[72ecbdf2]316                                           and not self.isBatch
317                                           and not self.isCalculating)
[ae34d30]318        self.calculateThisButton.setEnabled(self.logic.data_is_loaded
[72ecbdf2]319                                            and not self.isBatch
320                                            and not self.isCalculating)
[fa81e94]321        self.removeButton.setEnabled(self.logic.data_is_loaded)
[b1f6063]322        self.explorerButton.setEnabled(self.logic.data_is_loaded)
[72ecbdf2]323        self.stopButton.setVisible(self.isCalculating)
[b685c7b]324        self.regConstantSuggestionButton.setEnabled(
[d79bb7e]325            self.logic.data_is_loaded and
[b685c7b]326            self._calculator.suggested_alpha != self._calculator.alpha)
327        self.noOfTermsSuggestionButton.setEnabled(
[d79bb7e]328            self.logic.data_is_loaded and
[b685c7b]329            self._calculator.nfunc != self.nTermsSuggested)
[fa81e94]330
331    def populateDataComboBox(self, filename, data_ref):
332        """
333        Append a new file name to the data combobox
334        :param filename: data filename
335        :param data_ref: QStandardItem reference for data set to be added
336        """
[6a3e1fe]337        self.dataList.addItem(filename, data_ref)
[fa81e94]338
339    def acceptNoTerms(self):
340        """Send estimated no of terms to input"""
341        self.model.setItem(WIDGETS.W_NO_TERMS, QtGui.QStandardItem(
342            self.noOfTermsSuggestionButton.text()))
343
344    def acceptAlpha(self):
345        """Send estimated alpha to input"""
346        self.model.setItem(WIDGETS.W_REGULARIZATION, QtGui.QStandardItem(
347            self.regConstantSuggestionButton.text()))
348
[edd6720]349    def displayChange(self, data_index=0):
[47bf906]350        """Switch to another item in the data list"""
[044454d]351        if self.dataDeleted:
352            return
[edd6720]353        self.updateDataList(self._data)
354        self.setCurrentData(self.dataList.itemData(data_index))
[fa81e94]355
356    ######################################################################
357    # GUI Interaction Events
358
[ae34d30]359    def updateCalculator(self):
[fa81e94]360        """Update all p(r) params"""
[edd6720]361        self._calculator.set_x(self.logic.data.x)
362        self._calculator.set_y(self.logic.data.y)
363        self._calculator.set_err(self.logic.data.dy)
[441a03f]364        self.set_background(self.backgroundInput.text())
365
366    def set_background(self, value):
367        self._calculator.background = is_float(value)
[fa81e94]368
369    def model_changed(self):
370        """Update the values when user makes changes"""
371        if not self.mapper:
372            msg = "Unable to update P{r}. The connection between the main GUI "
373            msg += "and P(r) was severed. Attempting to restart P(r)."
[6da860a]374            logger.warning(msg)
[fa81e94]375            self.setClosable(True)
376            self.close()
[ae34d30]377            InversionWindow.__init__(self.parent(), list(self._dataList.keys()))
[fa81e94]378            exit(0)
[8f83719f]379        if self.dmaxWindow is not None:
[47bf906]380            self.dmaxWindow.nfunc = self.getNFunc()
[bb6b037]381            self.dmaxWindow.pr_state = self._calculator
[edd6720]382        self.mapper.toLast()
[fa81e94]383
384    def help(self):
385        """
386        Open the P(r) Inversion help browser
387        """
[aed0532]388        tree_location = "/user/qtgui/Perspectives/Inversion/pr_help.html"
[fa81e94]389
390        # Actual file anchor will depend on the combo box index
391        # Note that we can be clusmy here, since bad current_fitter_id
392        # will just make the page displayed from the top
[e90988c]393        self._manager.showHelp(tree_location)
[fa81e94]394
395    def toggleBgd(self):
396        """
397        Toggle the background between manual and estimated
398        """
[edd6720]399        if self.estimateBgd.isChecked():
400            self.manualBgd.setChecked(False)
[fa81e94]401            self.backgroundInput.setEnabled(False)
[304e42f]402            self._calculator.set_est_bck = True
[edd6720]403        elif self.manualBgd.isChecked():
404            self.estimateBgd.setChecked(False)
[fa81e94]405            self.backgroundInput.setEnabled(True)
[304e42f]406            self._calculator.set_est_bck = False
[441a03f]407        else:
408            pass
[fa81e94]409
410    def openExplorerWindow(self):
411        """
412        Open the Explorer window to see correlations between params and results
413        """
414        from .DMaxExplorerWidget import DmaxWindow
[6da860a]415        self.dmaxWindow = DmaxWindow(pr_state=self._calculator,
416                                     nfunc=self.getNFunc(),
417                                     parent=self)
[fa81e94]418        self.dmaxWindow.show()
419
[98485fe]420    def showBatchOutput(self):
[76567bb]421        """
422        Display the batch output in tabular form
423        :param output_data: Dictionary mapping filename -> P(r) instance
424        """
[ae34d30]425        if self.batchResultsWindow is None:
426            self.batchResultsWindow = BatchInversionOutputPanel(
[98485fe]427                parent=self, output_data=self.batchResults)
[76567bb]428        else:
[ae34d30]429            self.batchResultsWindow.setupTable(self.batchResults)
430        self.batchResultsWindow.show()
[effdd98]431
[72ecbdf2]432    def stopCalculation(self):
433        """ Stop all threads, return to the base state and update GUI """
[b0ba43e]434        self.stopCalcThread()
435        self.stopEstimationThread()
436        self.stopEstimateNTThread()
[72ecbdf2]437        # Show any batch calculations that successfully completed
438        if self.isBatch and self.batchResultsWindow is not None:
439            self.showBatchOutput()
440        self.isBatch = False
441        self.isCalculating = False
442        self.updateGuiValues()
443
[fa81e94]444    ######################################################################
445    # Response Actions
446
447    def setData(self, data_item=None, is_batch=False):
448        """
449        Assign new data set(s) to the P(r) perspective
450        Obtain a QStandardItem object and parse it to get Data1D/2D
451        Pass it over to the calculator
452        """
453        assert data_item is not None
454
455        if not isinstance(data_item, list):
456            msg = "Incorrect type passed to the P(r) Perspective"
[47bf906]457            raise AttributeError(msg)
[fa81e94]458
459        for data in data_item:
[ae34d30]460            if data in self._dataList.keys():
[8f83719f]461                # Don't add data if it's already in
[edd6720]462                continue
[fa81e94]463            # Create initial internal mappings
[edd6720]464            self.logic.data = GuiUtils.dataFromItem(data)
[14ec7dfd]465            if not isinstance(self.logic.data, Data1D):
466                msg = "P(r) perspective works for 1D data only"
467                logger.warning(msg)
468                continue
[e51e078]469            # Estimate q range
470            qmin, qmax = self.logic.computeDataRange()
471            self._calculator.set_qmin(qmin)
472            self._calculator.set_qmax(qmax)
[eeea6a3]473            if np.size(self.logic.data.dy) == 0 or np.all(self.logic.data.dy) == 0:
474                self.logic.data.dy = self._calculator.add_errors(self.logic.data.y)
[e51e078]475            self.updateDataList(data)
[edd6720]476            self.populateDataComboBox(self.logic.data.filename, data)
477        self.dataList.setCurrentIndex(len(self.dataList) - 1)
[14ec7dfd]478        #Checking for 1D again to mitigate the case when 2D data is last on the data list
479        if isinstance(self.logic.data, Data1D):
480            self.setCurrentData(data)
[fa81e94]481
[47bf906]482    def updateDataList(self, dataRef):
483        """Save the current data state of the window into self._data_list"""
[e51e078]484        if dataRef is None:
485            return
[ae34d30]486        self._dataList[dataRef] = {
[e51e078]487            DICT_KEYS[0]: self._calculator,
[ae34d30]488            DICT_KEYS[1]: self.prPlot,
489            DICT_KEYS[2]: self.dataPlot
[47bf906]490        }
[98485fe]491        # Update batch results window when finished
492        self.batchResults[self.logic.data.filename] = self._calculator
[ae34d30]493        if self.batchResultsWindow is not None:
[98485fe]494            self.showBatchOutput()
[47bf906]495
[fa81e94]496    def getNFunc(self):
497        """Get the n_func value from the GUI object"""
[50bfab0]498        try:
499            nfunc = int(self.noOfTermsInput.text())
500        except ValueError:
[6da860a]501            logger.error("Incorrect number of terms specified: %s"
[edd6720]502                          %self.noOfTermsInput.text())
[50bfab0]503            self.noOfTermsInput.setText(str(NUMBER_OF_TERMS))
504            nfunc = NUMBER_OF_TERMS
505        return nfunc
[fa81e94]506
507    def setCurrentData(self, data_ref):
[47bf906]508        """Get the data by reference and display as necessary"""
[304e42f]509        if data_ref is None:
510            return
[fa81e94]511        if not isinstance(data_ref, QtGui.QStandardItem):
512            msg = "Incorrect type passed to the P(r) Perspective"
[e51e078]513            raise AttributeError(msg)
[fa81e94]514        # Data references
515        self._data = data_ref
[edd6720]516        self.logic.data = GuiUtils.dataFromItem(data_ref)
[ae34d30]517        self._calculator = self._dataList[data_ref].get(DICT_KEYS[0])
518        self.prPlot = self._dataList[data_ref].get(DICT_KEYS[1])
519        self.dataPlot = self._dataList[data_ref].get(DICT_KEYS[2])
[edd6720]520        self.performEstimate()
[e51e078]521
[34cf92c]522    def updateDynamicGuiValues(self):
523        pr = self._calculator
524        alpha = self._calculator.suggested_alpha
525        self.model.setItem(WIDGETS.W_MAX_DIST,
526                            QtGui.QStandardItem("{:.4g}".format(pr.get_dmax())))
527        self.regConstantSuggestionButton.setText("{:-3.2g}".format(alpha))
528        self.noOfTermsSuggestionButton.setText(
529             "{:n}".format(self.nTermsSuggested))
530
531        self.enableButtons()
532
[e51e078]533    def updateGuiValues(self):
534        pr = self._calculator
535        out = self._calculator.out
536        cov = self._calculator.cov
537        elapsed = self._calculator.elapsed
538        alpha = self._calculator.suggested_alpha
539        self.model.setItem(WIDGETS.W_QMIN,
540                           QtGui.QStandardItem("{:.4g}".format(pr.get_qmin())))
541        self.model.setItem(WIDGETS.W_QMAX,
542                           QtGui.QStandardItem("{:.4g}".format(pr.get_qmax())))
543        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT,
[effdd98]544                           QtGui.QStandardItem("{:.3g}".format(pr.background)))
[e51e078]545        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT,
546                           QtGui.QStandardItem("{:.3g}".format(pr.background)))
547        self.model.setItem(WIDGETS.W_COMP_TIME,
548                           QtGui.QStandardItem("{:.4g}".format(elapsed)))
[ae34d30]549        self.model.setItem(WIDGETS.W_MAX_DIST,
550                           QtGui.QStandardItem("{:.4g}".format(pr.get_dmax())))
[e51e078]551
[304e42f]552        if isinstance(pr.chi2, np.ndarray):
[e51e078]553            self.model.setItem(WIDGETS.W_CHI_SQUARED,
554                               QtGui.QStandardItem("{:.3g}".format(pr.chi2[0])))
555        if out is not None:
556            self.model.setItem(WIDGETS.W_RG,
557                               QtGui.QStandardItem("{:.3g}".format(pr.rg(out))))
558            self.model.setItem(WIDGETS.W_I_ZERO,
559                               QtGui.QStandardItem(
560                                   "{:.3g}".format(pr.iq0(out))))
561            self.model.setItem(WIDGETS.W_OSCILLATION, QtGui.QStandardItem(
562                "{:.3g}".format(pr.oscillations(out))))
563            self.model.setItem(WIDGETS.W_POS_FRACTION, QtGui.QStandardItem(
564                "{:.3g}".format(pr.get_positive(out))))
565            if cov is not None:
566                self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION,
567                                   QtGui.QStandardItem(
568                                       "{:.3g}".format(
569                                           pr.get_pos_err(out, cov))))
[ae34d30]570        if self.prPlot is not None:
571            title = self.prPlot.name
[855e7ad]572            self.prPlot.plot_role = Data1D.ROLE_RESIDUAL
[ae34d30]573            GuiUtils.updateModelItemWithPlot(self._data, self.prPlot, title)
[9ce69ec]574            self.communicate.plotRequestedSignal.emit([self._data,self.prPlot], None)
[ae34d30]575        if self.dataPlot is not None:
576            title = self.dataPlot.name
[855e7ad]577            self.dataPlot.plot_role = Data1D.ROLE_DEFAULT
[9ce69ec]578            self.dataPlot.symbol = "Line"
579            self.dataPlot.show_errors = False
[ae34d30]580            GuiUtils.updateModelItemWithPlot(self._data, self.dataPlot, title)
[9ce69ec]581            self.communicate.plotRequestedSignal.emit([self._data,self.dataPlot], None)
[b685c7b]582        self.enableButtons()
[47bf906]583
[b9e89d5]584    def removeData(self, data_list=None):
[47bf906]585        """Remove the existing data reference from the P(r) Persepective"""
[044454d]586        self.dataDeleted = True
[d79bb7e]587        self.batchResults = {}
[b9e89d5]588        if not data_list:
589            data_list = [self._data]
[ae34d30]590        self.closeDMax()
[b9e89d5]591        for data in data_list:
[ae34d30]592            self._dataList.pop(data)
[76567bb]593        self._data = None
[044454d]594        length = len(self.dataList)
595        for index in reversed(range(length)):
[b9e89d5]596            if self.dataList.itemData(index) in data_list:
597                self.dataList.removeItem(index)
[e51e078]598        # Last file removed
[044454d]599        self.dataDeleted = False
[ae34d30]600        if len(self._dataList) == 0:
601            self.prPlot = None
602            self.dataPlot = None
[edd6720]603            self.logic.data = None
[ae34d30]604            self._calculator = Invertor()
605            self.closeBatchResults()
[b685c7b]606            self.nTermsSuggested = NUMBER_OF_TERMS
607            self.noOfTermsSuggestionButton.setText("{:n}".format(
608                self.nTermsSuggested))
609            self.regConstantSuggestionButton.setText("{:-3.2g}".format(
610                REGULARIZATION))
[edd6720]611            self.updateGuiValues()
[b685c7b]612            self.setupModel()
[ba4e3ba]613        else:
614            self.dataList.setCurrentIndex(0)
615            self.updateGuiValues()
[fa81e94]616
617    ######################################################################
618    # Thread Creators
[98485fe]619
[fa81e94]620    def startThreadAll(self):
[72ecbdf2]621        self.isCalculating = True
[98485fe]622        self.isBatch = True
[d79bb7e]623        self.batchComplete = []
624        self.calculateAllButton.setText("Calculating...")
625        self.enableButtons()
626        self.batchResultsWindow = BatchInversionOutputPanel(
627            parent=self, output_data=self.batchResults)
[98485fe]628        self.performEstimate()
629
630    def startNextBatchItem(self):
631        self.isBatch = False
[ae34d30]632        for index in range(len(self._dataList)):
[98485fe]633            if index not in self.batchComplete:
634                self.dataList.setCurrentIndex(index)
635                self.isBatch = True
[6da860a]636                # Add the index before calculating in case calculation fails
637                self.batchComplete.append(index)
[98485fe]638                break
639        if self.isBatch:
[76567bb]640            self.performEstimate()
[ae34d30]641        else:
642            # If no data sets left, end batch calculation
[72ecbdf2]643            self.isCalculating = False
[d79bb7e]644            self.batchComplete = []
[ae34d30]645            self.calculateAllButton.setText("Calculate All")
[5a5e371]646            self.showBatchOutput()
[ae34d30]647            self.enableButtons()
[fa81e94]648
649    def startThread(self):
650        """
651            Start a calculation thread
652        """
653        from .Thread import CalcPr
654
655        # Set data before running the calculations
[72ecbdf2]656        self.isCalculating = True
657        self.enableButtons()
[ae34d30]658        self.updateCalculator()
[6da860a]659        # Disable calculation buttons to prevent thread interference
[fa81e94]660
[b0ba43e]661        # If the thread is already started, stop it
662        self.stopCalcThread()
663
[fa81e94]664        pr = self._calculator.clone()
[4fbf0db]665        #Making sure that nfunc and alpha parameters are correctly initialized
666        pr.suggested_alpha = self._calculator.alpha
667        self.calcThread = CalcPr(pr, self.nTermsSuggested,
[ae34d30]668                                 error_func=self._threadError,
669                                 completefn=self._calculateCompleted,
670                                 updatefn=None)
671        self.calcThread.queue()
672        self.calcThread.ready(2.5)
[fa81e94]673
[b0ba43e]674    def stopCalcThread(self):
675        """ Stops a thread if it exists and is running """
676        if self.calcThread is not None and self.calcThread.isrunning():
677            self.calcThread.stop()
678
[fa81e94]679    def performEstimateNT(self):
680        """
[f1ec901]681        Perform parameter estimation
[fa81e94]682        """
683        from .Thread import EstimateNT
684
[ae34d30]685        self.updateCalculator()
[edd6720]686
[fa81e94]687        # If a thread is already started, stop it
[b0ba43e]688        self.stopEstimateNTThread()
689
[fa81e94]690        pr = self._calculator.clone()
691        # Skip the slit settings for the estimation
692        # It slows down the application and it doesn't change the estimates
693        pr.slit_height = 0.0
694        pr.slit_width = 0.0
695        nfunc = self.getNFunc()
[f1ec901]696
[ae34d30]697        self.estimationThreadNT = EstimateNT(pr, nfunc,
698                                             error_func=self._threadError,
699                                             completefn=self._estimateNTCompleted,
700                                             updatefn=None)
701        self.estimationThreadNT.queue()
702        self.estimationThreadNT.ready(2.5)
[fa81e94]703
[34cf92c]704    def performEstimateDynamicNT(self):
705        """
706        Perform parameter estimation
707        """
708        from .Thread import EstimateNT
709
710        self.updateCalculator()
711
712        # If a thread is already started, stop it
713        self.stopEstimateNTThread()
714
715        pr = self._calculator.clone()
716        # Skip the slit settings for the estimation
717        # It slows down the application and it doesn't change the estimates
718        pr.slit_height = 0.0
719        pr.slit_width = 0.0
720        nfunc = self.getNFunc()
721
722        self.estimationThreadNT = EstimateNT(pr, nfunc,
723                                             error_func=self._threadError,
724                                             completefn=self._estimateDynamicNTCompleted,
725                                             updatefn=None)
726        self.estimationThreadNT.queue()
727        self.estimationThreadNT.ready(2.5)
728
[b0ba43e]729    def stopEstimateNTThread(self):
730        if (self.estimationThreadNT is not None and
731                self.estimationThreadNT.isrunning()):
732            self.estimationThreadNT.stop()
733
[fa81e94]734    def performEstimate(self):
735        """
736            Perform parameter estimation
737        """
738        from .Thread import EstimatePr
739
740        # If a thread is already started, stop it
[b0ba43e]741        self.stopEstimationThread()
742
[ae34d30]743        self.estimationThread = EstimatePr(self._calculator.clone(),
744                                           self.getNFunc(),
745                                           error_func=self._threadError,
746                                           completefn=self._estimateCompleted,
747                                           updatefn=None)
748        self.estimationThread.queue()
749        self.estimationThread.ready(2.5)
[fa81e94]750
[34cf92c]751    def performEstimateDynamic(self):
752        """
753            Perform parameter estimation
754        """
755        from .Thread import EstimatePr
756
757        # If a thread is already started, stop it
758        self.stopEstimationThread()
759
760        self.estimationThread = EstimatePr(self._calculator.clone(),
761                                           self.getNFunc(),
762                                           error_func=self._threadError,
763                                           completefn=self._estimateDynamicCompleted,
764                                           updatefn=None)
765        self.estimationThread.queue()
766        self.estimationThread.ready(2.5)
767
[b0ba43e]768    def stopEstimationThread(self):
769        """ Stop the estimation thread if it exists and is running """
770        if (self.estimationThread is not None and
771                self.estimationThread.isrunning()):
772            self.estimationThread.stop()
773
[fa81e94]774    ######################################################################
775    # Thread Complete
776
777    def _estimateCompleted(self, alpha, message, elapsed):
[f1ec901]778        ''' Send a signal to the main thread for model update'''
779        self.estimateSignal.emit((alpha, message, elapsed))
780
[34cf92c]781    def _estimateDynamicCompleted(self, alpha, message, elapsed):
782        ''' Send a signal to the main thread for model update'''
783        self.estimateDynamicSignal.emit((alpha, message, elapsed))
784
[f1ec901]785    def _estimateUpdate(self, output_tuple):
[fa81e94]786        """
787        Parameter estimation completed,
788        display the results to the user
789
790        :param alpha: estimated best alpha
791        :param elapsed: computation time
792        """
[f1ec901]793        alpha, message, elapsed = output_tuple
[6da860a]794        self._calculator.alpha = alpha
795        self._calculator.elapsed += self._calculator.elapsed
[fa81e94]796        if message:
[6da860a]797            logger.info(message)
[98485fe]798        self.performEstimateNT()
[28965e9]799        self.performEstimateDynamicNT()
[fa81e94]800
[34cf92c]801    def _estimateDynamicUpdate(self, output_tuple):
802        """
803        Parameter estimation completed,
804        display the results to the user
805
806        :param alpha: estimated best alpha
807        :param elapsed: computation time
808        """
809        alpha, message, elapsed = output_tuple
810        self._calculator.alpha = alpha
811        self._calculator.elapsed += self._calculator.elapsed
812        if message:
813            logger.info(message)
814        self.performEstimateDynamicNT()
815
[fa81e94]816    def _estimateNTCompleted(self, nterms, alpha, message, elapsed):
[f1ec901]817        ''' Send a signal to the main thread for model update'''
818        self.estimateNTSignal.emit((nterms, alpha, message, elapsed))
819
[34cf92c]820    def _estimateDynamicNTCompleted(self, nterms, alpha, message, elapsed):
821        ''' Send a signal to the main thread for model update'''
822        self.estimateDynamicNTSignal.emit((nterms, alpha, message, elapsed))
823
[f1ec901]824    def _estimateNTUpdate(self, output_tuple):
[fa81e94]825        """
826        Parameter estimation completed,
827        display the results to the user
828
829        :param alpha: estimated best alpha
830        :param nterms: estimated number of terms
831        :param elapsed: computation time
832        """
[f1ec901]833        nterms, alpha, message, elapsed = output_tuple
[6da860a]834        self._calculator.elapsed += elapsed
[e51e078]835        self._calculator.suggested_alpha = alpha
836        self.nTermsSuggested = nterms
[fa81e94]837        # Save useful info
[e51e078]838        self.updateGuiValues()
[fa81e94]839        if message:
[6da860a]840            logger.info(message)
[98485fe]841        if self.isBatch:
842            self.acceptAlpha()
843            self.acceptNoTerms()
844            self.startThread()
[fa81e94]845
[34cf92c]846    def _estimateDynamicNTUpdate(self, output_tuple):
847        """
848        Parameter estimation completed,
849        display the results to the user
850
851        :param alpha: estimated best alpha
852        :param nterms: estimated number of terms
853        :param elapsed: computation time
854        """
855        nterms, alpha, message, elapsed = output_tuple
856        self._calculator.elapsed += elapsed
857        self._calculator.suggested_alpha = alpha
858        self.nTermsSuggested = nterms
859        # Save useful info
860        self.updateDynamicGuiValues()
861        if message:
862            logger.info(message)
863        if self.isBatch:
864            self.acceptAlpha()
865            self.acceptNoTerms()
866            self.startThread()
867
[f1ec901]868    def _calculateCompleted(self, out, cov, pr, elapsed):
869        ''' Send a signal to the main thread for model update'''
870        self.calculateSignal.emit((out, cov, pr, elapsed))
871
872    def _calculateUpdate(self, output_tuple):
[fa81e94]873        """
874        Method called with the results when the inversion is done
875
876        :param out: output coefficient for the base functions
877        :param cov: covariance matrix
878        :param pr: Invertor instance
879        :param elapsed: time spent computing
880        """
[f1ec901]881        out, cov, pr, elapsed = output_tuple
[fa81e94]882        # Save useful info
883        cov = np.ascontiguousarray(cov)
884        pr.cov = cov
885        pr.out = out
886        pr.elapsed = elapsed
887
888        # Save Pr invertor
889        self._calculator = pr
[f1ec901]890
[318b353e]891        # Update P(r) and fit plots
[ae34d30]892        self.prPlot = self.logic.newPRPlot(out, self._calculator, cov)
893        self.prPlot.filename = self.logic.data.filename
894        self.dataPlot = self.logic.new1DPlot(out, self._calculator)
895        self.dataPlot.filename = self.logic.data.filename
[318b353e]896
897        # Udpate internals and GUI
[e51e078]898        self.updateDataList(self._data)
[98485fe]899        if self.isBatch:
900            self.batchComplete.append(self.dataList.currentIndex())
901            self.startNextBatchItem()
[72ecbdf2]902        else:
903            self.isCalculating = False
904        self.updateGuiValues()
[fa81e94]905
906    def _threadError(self, error):
907        """
908            Call-back method for calculation errors
909        """
[6da860a]910        logger.error(error)
[b0ba43e]911        if self.isBatch:
912            self.startNextBatchItem()
913        else:
914            self.stopCalculation()
Note: See TracBrowser for help on using the repository browser.