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, 2 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
Line 
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
16from sas.qtgui.Plotting.PlotterData import Data1D
17# Batch calculation display
18from sas.qtgui.Utilities.GridPanel import BatchInversionOutputPanel
19
20
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
28
29NUMBER_OF_TERMS = 10
30REGULARIZATION = 0.0001
31BACKGROUND_INPUT = 0.0
32MAX_DIST = 140.0
33DICT_KEYS = ["Calculator", "PrPlot", "DataPlot"]
34
35logger = logging.getLogger(__name__)
36
37
38class InversionWindow(QtWidgets.QDialog, Ui_PrInversion):
39    """
40    The main window for the P(r) Inversion perspective.
41    """
42
43    name = "Inversion"
44    estimateSignal = QtCore.pyqtSignal(tuple)
45    estimateNTSignal = QtCore.pyqtSignal(tuple)
46    estimateDynamicNTSignal = QtCore.pyqtSignal(tuple)
47    estimateDynamicSignal = QtCore.pyqtSignal(tuple)
48    calculateSignal = QtCore.pyqtSignal(tuple)
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
57        self._parent = parent
58        self.communicate = parent.communicator()
59        self.communicate.dataDeletedSignal.connect(self.removeData)
60
61        self.logic = InversionLogic()
62
63        # The window should not close
64        self._allowClose = False
65
66        # Visible data items
67        # current QStandardItem showing on the panel
68        self._data = None
69        # Reference to Dmax window for self._data
70        self.dmaxWindow = None
71        # p(r) calculator for self._data
72        self._calculator = Invertor()
73        # Default to background estimate
74        self._calculator.est_bck = True
75        # plots of self._data
76        self.prPlot = None
77        self.dataPlot = None
78        # suggested nTerms
79        self.nTermsSuggested = NUMBER_OF_TERMS
80
81        # Calculation threads used by all data items
82        self.calcThread = None
83        self.estimationThread = None
84        self.estimationThreadNT = None
85        self.isCalculating = False
86
87        # Mapping for all data items
88        # Dictionary mapping data to all parameters
89        self._dataList = {}
90        if not isinstance(data, list):
91            data_list = [data]
92        if data is not None:
93            for datum in data_list:
94                self.updateDataList(datum)
95
96        self.dataDeleted = False
97
98        self.model = QtGui.QStandardItemModel(self)
99        self.mapper = QtWidgets.QDataWidgetMapper(self)
100
101        # Batch fitting parameters
102        self.isBatch = False
103        self.batchResultsWindow = None
104        self.batchResults = {}
105        self.batchComplete = []
106
107        # Add validators
108        self.setupValidators()
109        # Link user interactions with methods
110        self.setupLinks()
111        # Set values
112        self.setupModel()
113        # Set up the Widget Map
114        self.setupMapper()
115
116        #Hidding calculate all buton
117        self.calculateAllButton.setVisible(False)
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):
128        return False
129
130    def setClosable(self, value=True):
131        """
132        Allow outsiders close this widget
133        """
134        assert isinstance(value, bool)
135        self._allowClose = value
136
137    def isClosable(self):
138        """
139        Allow outsiders close this widget
140        """
141        return self._allowClose
142
143    def closeEvent(self, event):
144        """
145        Overwrite QDialog close method to allow for custom widget close
146        """
147        # Close report widgets before closing/minimizing main widget
148        self.closeDMax()
149        self.closeBatchResults()
150        if self._allowClose:
151            # reset the closability flag
152            self.setClosable(value=False)
153            # Tell the MdiArea to close the container
154            self.parentWidget().close()
155            event.accept()
156        else:
157            event.ignore()
158            # Maybe we should just minimize
159            self.setWindowState(QtCore.Qt.WindowMinimized)
160
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
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)
175        self.calculateAllButton.clicked.connect(self.startThreadAll)
176        self.calculateThisButton.clicked.connect(self.startThread)
177        self.stopButton.clicked.connect(self.stopCalculation)
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)
185
186        self.backgroundInput.textChanged.connect(
187            lambda: self.set_background(self.backgroundInput.text()))
188        self.minQInput.textChanged.connect(
189            lambda: self._calculator.set_qmin(is_float(self.minQInput.text())))
190        self.regularizationConstantInput.textChanged.connect(
191            lambda: self._calculator.set_alpha(is_float(self.regularizationConstantInput.text())))
192        self.maxDistanceInput.textChanged.connect(
193            lambda: self._calculator.set_dmax(is_float(self.maxDistanceInput.text())))
194        self.maxQInput.textChanged.connect(
195            lambda: self._calculator.set_qmax(is_float(self.maxQInput.text())))
196        self.slitHeightInput.textChanged.connect(
197            lambda: self._calculator.set_slit_height(is_float(self.slitHeightInput.text())))
198        self.slitWidthInput.textChanged.connect(
199            lambda: self._calculator.set_slit_width(is_float(self.slitWidthInput.text())))
200
201        self.model.itemChanged.connect(self.model_changed)
202        self.estimateNTSignal.connect(self._estimateNTUpdate)
203        self.estimateDynamicNTSignal.connect(self._estimateDynamicNTUpdate)
204        self.estimateDynamicSignal.connect(self._estimateDynamicUpdate)
205        self.estimateSignal.connect(self._estimateUpdate)
206        self.calculateSignal.connect(self._calculateUpdate)
207
208        self.maxDistanceInput.textEdited.connect(self.performEstimateDynamic)
209
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
231        self.mapper.addMapping(self.regularizationConstantInput, WIDGETS.W_REGULARIZATION)
232        self.mapper.addMapping(self.regConstantSuggestionButton, WIDGETS.W_REGULARIZATION_SUGGEST)
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)
236        self.mapper.addMapping(self.noOfTermsSuggestionButton, WIDGETS.W_NO_TERMS_SUGGEST)
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)
246        self.mapper.addMapping(self.sigmaPosFractionValue, WIDGETS.W_SIGMA_POS_FRACTION)
247
248        # Main Buttons
249        self.mapper.addMapping(self.removeButton, WIDGETS.W_REMOVE)
250        self.mapper.addMapping(self.calculateAllButton, WIDGETS.W_CALCULATE_ALL)
251        self.mapper.addMapping(self.calculateThisButton, WIDGETS.W_CALCULATE_VISIBLE)
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        """
260        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT))
261        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, bgd_item)
262        blank_item = QtGui.QStandardItem("")
263        self.model.setItem(WIDGETS.W_QMIN, blank_item)
264        blank_item = QtGui.QStandardItem("")
265        self.model.setItem(WIDGETS.W_QMAX, blank_item)
266        blank_item = QtGui.QStandardItem("")
267        self.model.setItem(WIDGETS.W_SLIT_WIDTH, blank_item)
268        blank_item = QtGui.QStandardItem("")
269        self.model.setItem(WIDGETS.W_SLIT_HEIGHT, blank_item)
270        no_terms_item = QtGui.QStandardItem(str(NUMBER_OF_TERMS))
271        self.model.setItem(WIDGETS.W_NO_TERMS, no_terms_item)
272        reg_item = QtGui.QStandardItem(str(REGULARIZATION))
273        self.model.setItem(WIDGETS.W_REGULARIZATION, reg_item)
274        max_dist_item = QtGui.QStandardItem(str(MAX_DIST))
275        self.model.setItem(WIDGETS.W_MAX_DIST, max_dist_item)
276        blank_item = QtGui.QStandardItem("")
277        self.model.setItem(WIDGETS.W_RG, blank_item)
278        blank_item = QtGui.QStandardItem("")
279        self.model.setItem(WIDGETS.W_I_ZERO, blank_item)
280        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT))
281        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, bgd_item)
282        blank_item = QtGui.QStandardItem("")
283        self.model.setItem(WIDGETS.W_COMP_TIME, blank_item)
284        blank_item = QtGui.QStandardItem("")
285        self.model.setItem(WIDGETS.W_CHI_SQUARED, blank_item)
286        blank_item = QtGui.QStandardItem("")
287        self.model.setItem(WIDGETS.W_OSCILLATION, blank_item)
288        blank_item = QtGui.QStandardItem("")
289        self.model.setItem(WIDGETS.W_POS_FRACTION, blank_item)
290        blank_item = QtGui.QStandardItem("")
291        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, blank_item)
292
293    def setupWindow(self):
294        """Initialize base window state on init"""
295        self.enableButtons()
296        self.estimateBgd.setChecked(True)
297
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
308    ######################################################################
309    # Methods for updating GUI
310
311    def enableButtons(self):
312        """
313        Enable buttons when data is present, else disable them
314        """
315        self.calculateAllButton.setEnabled(len(self._dataList) > 1
316                                           and not self.isBatch
317                                           and not self.isCalculating)
318        self.calculateThisButton.setEnabled(self.logic.data_is_loaded
319                                            and not self.isBatch
320                                            and not self.isCalculating)
321        self.removeButton.setEnabled(self.logic.data_is_loaded)
322        self.explorerButton.setEnabled(self.logic.data_is_loaded)
323        self.stopButton.setVisible(self.isCalculating)
324        self.regConstantSuggestionButton.setEnabled(
325            self.logic.data_is_loaded and
326            self._calculator.suggested_alpha != self._calculator.alpha)
327        self.noOfTermsSuggestionButton.setEnabled(
328            self.logic.data_is_loaded and
329            self._calculator.nfunc != self.nTermsSuggested)
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        """
337        self.dataList.addItem(filename, data_ref)
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
349    def displayChange(self, data_index=0):
350        """Switch to another item in the data list"""
351        if self.dataDeleted:
352            return
353        self.updateDataList(self._data)
354        self.setCurrentData(self.dataList.itemData(data_index))
355
356    ######################################################################
357    # GUI Interaction Events
358
359    def updateCalculator(self):
360        """Update all p(r) params"""
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)
364        self.set_background(self.backgroundInput.text())
365
366    def set_background(self, value):
367        self._calculator.background = is_float(value)
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)."
374            logger.warning(msg)
375            self.setClosable(True)
376            self.close()
377            InversionWindow.__init__(self.parent(), list(self._dataList.keys()))
378            exit(0)
379        if self.dmaxWindow is not None:
380            self.dmaxWindow.nfunc = self.getNFunc()
381            self.dmaxWindow.pr_state = self._calculator
382        self.mapper.toLast()
383
384    def help(self):
385        """
386        Open the P(r) Inversion help browser
387        """
388        tree_location = "/user/qtgui/Perspectives/Inversion/pr_help.html"
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
393        self._manager.showHelp(tree_location)
394
395    def toggleBgd(self):
396        """
397        Toggle the background between manual and estimated
398        """
399        if self.estimateBgd.isChecked():
400            self.manualBgd.setChecked(False)
401            self.backgroundInput.setEnabled(False)
402            self._calculator.set_est_bck = True
403        elif self.manualBgd.isChecked():
404            self.estimateBgd.setChecked(False)
405            self.backgroundInput.setEnabled(True)
406            self._calculator.set_est_bck = False
407        else:
408            pass
409
410    def openExplorerWindow(self):
411        """
412        Open the Explorer window to see correlations between params and results
413        """
414        from .DMaxExplorerWidget import DmaxWindow
415        self.dmaxWindow = DmaxWindow(pr_state=self._calculator,
416                                     nfunc=self.getNFunc(),
417                                     parent=self)
418        self.dmaxWindow.show()
419
420    def showBatchOutput(self):
421        """
422        Display the batch output in tabular form
423        :param output_data: Dictionary mapping filename -> P(r) instance
424        """
425        if self.batchResultsWindow is None:
426            self.batchResultsWindow = BatchInversionOutputPanel(
427                parent=self, output_data=self.batchResults)
428        else:
429            self.batchResultsWindow.setupTable(self.batchResults)
430        self.batchResultsWindow.show()
431
432    def stopCalculation(self):
433        """ Stop all threads, return to the base state and update GUI """
434        self.stopCalcThread()
435        self.stopEstimationThread()
436        self.stopEstimateNTThread()
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
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"
457            raise AttributeError(msg)
458
459        for data in data_item:
460            if data in self._dataList.keys():
461                # Don't add data if it's already in
462                continue
463            # Create initial internal mappings
464            self.logic.data = GuiUtils.dataFromItem(data)
465            if not isinstance(self.logic.data, Data1D):
466                msg = "P(r) perspective works for 1D data only"
467                logger.warning(msg)
468                continue
469            # Estimate q range
470            qmin, qmax = self.logic.computeDataRange()
471            self._calculator.set_qmin(qmin)
472            self._calculator.set_qmax(qmax)
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)
475            self.updateDataList(data)
476            self.populateDataComboBox(self.logic.data.filename, data)
477        self.dataList.setCurrentIndex(len(self.dataList) - 1)
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)
481
482    def updateDataList(self, dataRef):
483        """Save the current data state of the window into self._data_list"""
484        if dataRef is None:
485            return
486        self._dataList[dataRef] = {
487            DICT_KEYS[0]: self._calculator,
488            DICT_KEYS[1]: self.prPlot,
489            DICT_KEYS[2]: self.dataPlot
490        }
491        # Update batch results window when finished
492        self.batchResults[self.logic.data.filename] = self._calculator
493        if self.batchResultsWindow is not None:
494            self.showBatchOutput()
495
496    def getNFunc(self):
497        """Get the n_func value from the GUI object"""
498        try:
499            nfunc = int(self.noOfTermsInput.text())
500        except ValueError:
501            logger.error("Incorrect number of terms specified: %s"
502                          %self.noOfTermsInput.text())
503            self.noOfTermsInput.setText(str(NUMBER_OF_TERMS))
504            nfunc = NUMBER_OF_TERMS
505        return nfunc
506
507    def setCurrentData(self, data_ref):
508        """Get the data by reference and display as necessary"""
509        if data_ref is None:
510            return
511        if not isinstance(data_ref, QtGui.QStandardItem):
512            msg = "Incorrect type passed to the P(r) Perspective"
513            raise AttributeError(msg)
514        # Data references
515        self._data = data_ref
516        self.logic.data = GuiUtils.dataFromItem(data_ref)
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])
520        self.performEstimate()
521
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
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,
544                           QtGui.QStandardItem("{:.3g}".format(pr.background)))
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)))
549        self.model.setItem(WIDGETS.W_MAX_DIST,
550                           QtGui.QStandardItem("{:.4g}".format(pr.get_dmax())))
551
552        if isinstance(pr.chi2, np.ndarray):
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))))
570        if self.prPlot is not None:
571            title = self.prPlot.name
572            self.prPlot.plot_role = Data1D.ROLE_RESIDUAL
573            GuiUtils.updateModelItemWithPlot(self._data, self.prPlot, title)
574            self.communicate.plotRequestedSignal.emit([self._data,self.prPlot], None)
575        if self.dataPlot is not None:
576            title = self.dataPlot.name
577            self.dataPlot.plot_role = Data1D.ROLE_DEFAULT
578            self.dataPlot.symbol = "Line"
579            self.dataPlot.show_errors = False
580            GuiUtils.updateModelItemWithPlot(self._data, self.dataPlot, title)
581            self.communicate.plotRequestedSignal.emit([self._data,self.dataPlot], None)
582        self.enableButtons()
583
584    def removeData(self, data_list=None):
585        """Remove the existing data reference from the P(r) Persepective"""
586        self.dataDeleted = True
587        self.batchResults = {}
588        if not data_list:
589            data_list = [self._data]
590        self.closeDMax()
591        for data in data_list:
592            self._dataList.pop(data)
593        self._data = None
594        length = len(self.dataList)
595        for index in reversed(range(length)):
596            if self.dataList.itemData(index) in data_list:
597                self.dataList.removeItem(index)
598        # Last file removed
599        self.dataDeleted = False
600        if len(self._dataList) == 0:
601            self.prPlot = None
602            self.dataPlot = None
603            self.logic.data = None
604            self._calculator = Invertor()
605            self.closeBatchResults()
606            self.nTermsSuggested = NUMBER_OF_TERMS
607            self.noOfTermsSuggestionButton.setText("{:n}".format(
608                self.nTermsSuggested))
609            self.regConstantSuggestionButton.setText("{:-3.2g}".format(
610                REGULARIZATION))
611            self.updateGuiValues()
612            self.setupModel()
613        else:
614            self.dataList.setCurrentIndex(0)
615            self.updateGuiValues()
616
617    ######################################################################
618    # Thread Creators
619
620    def startThreadAll(self):
621        self.isCalculating = True
622        self.isBatch = True
623        self.batchComplete = []
624        self.calculateAllButton.setText("Calculating...")
625        self.enableButtons()
626        self.batchResultsWindow = BatchInversionOutputPanel(
627            parent=self, output_data=self.batchResults)
628        self.performEstimate()
629
630    def startNextBatchItem(self):
631        self.isBatch = False
632        for index in range(len(self._dataList)):
633            if index not in self.batchComplete:
634                self.dataList.setCurrentIndex(index)
635                self.isBatch = True
636                # Add the index before calculating in case calculation fails
637                self.batchComplete.append(index)
638                break
639        if self.isBatch:
640            self.performEstimate()
641        else:
642            # If no data sets left, end batch calculation
643            self.isCalculating = False
644            self.batchComplete = []
645            self.calculateAllButton.setText("Calculate All")
646            self.showBatchOutput()
647            self.enableButtons()
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
656        self.isCalculating = True
657        self.enableButtons()
658        self.updateCalculator()
659        # Disable calculation buttons to prevent thread interference
660
661        # If the thread is already started, stop it
662        self.stopCalcThread()
663
664        pr = self._calculator.clone()
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,
668                                 error_func=self._threadError,
669                                 completefn=self._calculateCompleted,
670                                 updatefn=None)
671        self.calcThread.queue()
672        self.calcThread.ready(2.5)
673
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
679    def performEstimateNT(self):
680        """
681        Perform parameter estimation
682        """
683        from .Thread import EstimateNT
684
685        self.updateCalculator()
686
687        # If a thread is already started, stop it
688        self.stopEstimateNTThread()
689
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()
696
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)
703
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
729    def stopEstimateNTThread(self):
730        if (self.estimationThreadNT is not None and
731                self.estimationThreadNT.isrunning()):
732            self.estimationThreadNT.stop()
733
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
741        self.stopEstimationThread()
742
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)
750
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
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
774    ######################################################################
775    # Thread Complete
776
777    def _estimateCompleted(self, alpha, message, elapsed):
778        ''' Send a signal to the main thread for model update'''
779        self.estimateSignal.emit((alpha, message, elapsed))
780
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
785    def _estimateUpdate(self, output_tuple):
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        """
793        alpha, message, elapsed = output_tuple
794        self._calculator.alpha = alpha
795        self._calculator.elapsed += self._calculator.elapsed
796        if message:
797            logger.info(message)
798        self.performEstimateNT()
799        self.performEstimateDynamicNT()
800
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
816    def _estimateNTCompleted(self, nterms, alpha, message, elapsed):
817        ''' Send a signal to the main thread for model update'''
818        self.estimateNTSignal.emit((nterms, alpha, message, elapsed))
819
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
824    def _estimateNTUpdate(self, output_tuple):
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        """
833        nterms, alpha, message, elapsed = output_tuple
834        self._calculator.elapsed += elapsed
835        self._calculator.suggested_alpha = alpha
836        self.nTermsSuggested = nterms
837        # Save useful info
838        self.updateGuiValues()
839        if message:
840            logger.info(message)
841        if self.isBatch:
842            self.acceptAlpha()
843            self.acceptNoTerms()
844            self.startThread()
845
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
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):
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        """
881        out, cov, pr, elapsed = output_tuple
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
890
891        # Update P(r) and fit plots
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
896
897        # Udpate internals and GUI
898        self.updateDataList(self._data)
899        if self.isBatch:
900            self.batchComplete.append(self.dataList.currentIndex())
901            self.startNextBatchItem()
902        else:
903            self.isCalculating = False
904        self.updateGuiValues()
905
906    def _threadError(self, error):
907        """
908            Call-back method for calculation errors
909        """
910        logger.error(error)
911        if self.isBatch:
912            self.startNextBatchItem()
913        else:
914            self.stopCalculation()
Note: See TracBrowser for help on using the repository browser.