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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since d132f33 was d132f33, checked in by krzywon, 7 years ago

Start of P(r) batch capability work.

  • Property mode set to 100644
File size: 20.0 KB
Line 
1import sys
2import logging
3import pylab
4import numpy as np
5
6from PyQt4 import QtGui, QtCore, QtWebKit
7from twisted.internet import reactor
8
9# sas-global
10import sas.qtgui.Utilities.GuiUtils as GuiUtils
11
12# pr inversion GUI elements
13from InversionUtils import WIDGETS
14import UI.TabbedInversionUI
15from UI.TabbedInversionUI import Ui_PrInversion
16from InversionLogic import InversionLogic
17
18# pr inversion calculation elements
19from sas.sascalc.dataloader.data_info import Data1D
20from sas.sascalc.pr.invertor import Invertor
21
22def is_float(value):
23    try:
24        return float(value)
25    except ValueError:
26        return 0.0
27
28class InversionWindow(QtGui.QTabWidget, Ui_PrInversion):
29    """
30    The main window for the P(r) Inversion perspective.
31    """
32
33    name = "Inversion"
34
35    def __init__(self, parent=None, data=None):
36        super(InversionWindow, self).__init__()
37        self.setupUi(self)
38
39        self.setWindowTitle("P(r) Inversion Perspective")
40
41        self._manager = parent
42        self._model_item = QtGui.QStandardItem()
43        self._helpView = QtWebKit.QWebView()
44
45        self.communicate = GuiUtils.Communicate()
46
47        self.logic = InversionLogic()
48
49        # The window should not close
50        self._allow_close = False
51
52        # current QStandardItem showing on the panel
53        self._data = None
54        # current Data1D as referenced by self._data
55        self._data_set = None
56
57        # p(r) calculator
58        self._calculator = Invertor()
59        self._last_calculator = None
60        self.calc_thread = None
61        self.estimation_thread = None
62
63        # Current data object in view
64        self._data_index = 0
65        # list mapping data to p(r) calculation
66        self._data_list = {}
67        if not isinstance(data, list):
68            data_list = [data]
69        if data is not None:
70            for datum in data_list:
71                self._data_list[datum] = self._calculator.clone()
72
73        # plots
74        self.pr_plot = None
75        self.data_plot = None
76
77        self.model = QtGui.QStandardItemModel(self)
78        self.mapper = QtGui.QDataWidgetMapper(self)
79        # Link user interactions with methods
80        self.setupLinks()
81        # Set values
82        self.setupModel()
83        # Set up the Widget Map
84        self.setupMapper()
85
86    ######################################################################
87    # Base Perspective Class Definitions
88
89    def communicator(self):
90        return self.communicate
91
92    def allowBatch(self):
93        return True
94
95    def setClosable(self, value=True):
96        """
97        Allow outsiders close this widget
98        """
99        assert isinstance(value, bool)
100        self._allow_close = value
101
102    def closeEvent(self, event):
103        """
104        Overwrite QDialog close method to allow for custom widget close
105        """
106        if self._allow_close:
107            # reset the closability flag
108            self.setClosable(value=False)
109            event.accept()
110        else:
111            event.ignore()
112            # Maybe we should just minimize
113            self.setWindowState(QtCore.Qt.WindowMinimized)
114
115    ######################################################################
116    # Initialization routines
117
118    def setupLinks(self):
119        """Connect the use controls to their appropriate methods"""
120        self.enableButtons()
121        self.dataList.currentIndexChanged.connect(self.displayChange)
122        self.calculateButton.clicked.connect(self._calculation)
123        self.helpButton.clicked.connect(self.help)
124        self.estimateBgd.toggled.connect(self.toggleBgd)
125        self.manualBgd.toggled.connect(self.toggleBgd)
126        self.regConstantSuggestionButton.clicked.connect(self.acceptAlpha)
127        self.noOfTermsSuggestionButton.clicked.connect(self.acceptNoTerms)
128        self.explorerButton.clicked.connect(self.openExplorerWindow)
129        self.backgroundInput.textChanged.connect(
130            lambda: self._calculator.set_est_bck(int(is_float(
131                str(self.backgroundInput.text())))))
132        self.minQInput.textChanged.connect(
133            lambda: self._calculator.set_qmin(is_float(
134                str(self.minQInput.text()))))
135        self.regularizationConstantInput.textChanged.connect(
136            lambda: self._calculator.set_alpha(is_float(
137                str(self.regularizationConstantInput.text()))))
138        self.maxDistanceInput.textChanged.connect(
139            lambda: self._calculator.set_dmax(is_float(
140                str(self.maxDistanceInput.text()))))
141        self.maxQInput.textChanged.connect(
142            lambda: self._calculator.set_qmax(is_float(
143                str(self.maxQInput.text()))))
144        self.slitHeightInput.textChanged.connect(
145            lambda: self._calculator.set_slit_height(is_float(
146                str(self.slitHeightInput.text()))))
147        self.slitWidthInput.textChanged.connect(
148            lambda: self._calculator.set_slit_width(is_float(
149                str(self.slitHeightInput.text()))))
150        self.model.itemChanged.connect(self.model_changed)
151        self.estimateBgd.setChecked(True)
152
153    def setupMapper(self):
154        # Set up the mapper.
155        self.mapper.setOrientation(QtCore.Qt.Vertical)
156        self.mapper.setModel(self.model)
157
158        # Filename
159        self.mapper.addMapping(self.dataList, WIDGETS.W_FILENAME)
160        # Background
161        self.mapper.addMapping(self.backgroundInput, WIDGETS.W_BACKGROUND_INPUT)
162        self.mapper.addMapping(self.estimateBgd, WIDGETS.W_ESTIMATE)
163        self.mapper.addMapping(self.manualBgd, WIDGETS.W_MANUAL_INPUT)
164
165        # Qmin/Qmax
166        self.mapper.addMapping(self.minQInput, WIDGETS.W_QMIN)
167        self.mapper.addMapping(self.maxQInput, WIDGETS.W_QMAX)
168
169        # Slit Parameter items
170        self.mapper.addMapping(self.slitWidthInput, WIDGETS.W_SLIT_WIDTH)
171        self.mapper.addMapping(self.slitHeightInput, WIDGETS.W_SLIT_HEIGHT)
172
173        # Parameter Items
174        self.mapper.addMapping(self.regularizationConstantInput,
175                               WIDGETS.W_REGULARIZATION)
176        self.mapper.addMapping(self.regConstantSuggestionButton,
177                               WIDGETS.W_REGULARIZATION_SUGGEST)
178        self.mapper.addMapping(self.explorerButton, WIDGETS.W_EXPLORE)
179        self.mapper.addMapping(self.maxDistanceInput, WIDGETS.W_MAX_DIST)
180        self.mapper.addMapping(self.noOfTermsInput, WIDGETS.W_NO_TERMS)
181        self.mapper.addMapping(self.noOfTermsSuggestionButton,
182                               WIDGETS.W_NO_TERMS_SUGGEST)
183
184        # Output
185        self.mapper.addMapping(self.rgValue, WIDGETS.W_RG)
186        self.mapper.addMapping(self.iQ0Value, WIDGETS.W_I_ZERO)
187        self.mapper.addMapping(self.backgroundValue, WIDGETS.W_BACKGROUND_OUTPUT)
188        self.mapper.addMapping(self.computationTimeValue, WIDGETS.W_COMP_TIME)
189        self.mapper.addMapping(self.chiDofValue, WIDGETS.W_CHI_SQUARED)
190        self.mapper.addMapping(self.oscillationValue, WIDGETS.W_OSCILLATION)
191        self.mapper.addMapping(self.posFractionValue, WIDGETS.W_POS_FRACTION)
192        self.mapper.addMapping(self.sigmaPosFractionValue,
193                               WIDGETS.W_SIGMA_POS_FRACTION)
194
195        # Main Buttons
196        self.mapper.addMapping(self.calculateButton, WIDGETS.W_CALCULATE)
197        self.mapper.addMapping(self.helpButton, WIDGETS.W_HELP)
198
199        self.mapper.toFirst()
200
201    def setupModel(self):
202        """
203        Update boxes with initial values
204        """
205        item = QtGui.QStandardItem("")
206        self.model.setItem(WIDGETS.W_FILENAME, item)
207        item = QtGui.QStandardItem('0.0')
208        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, item)
209        item = QtGui.QStandardItem("")
210        self.model.setItem(WIDGETS.W_QMIN, item)
211        item = QtGui.QStandardItem("")
212        self.model.setItem(WIDGETS.W_QMAX, item)
213        item = QtGui.QStandardItem("")
214        self.model.setItem(WIDGETS.W_SLIT_WIDTH, item)
215        item = QtGui.QStandardItem("")
216        self.model.setItem(WIDGETS.W_SLIT_HEIGHT, item)
217        item = QtGui.QStandardItem("10")
218        self.model.setItem(WIDGETS.W_NO_TERMS, item)
219        item = QtGui.QStandardItem("0.0001")
220        self.model.setItem(WIDGETS.W_REGULARIZATION, item)
221        item = QtGui.QStandardItem("140.0")
222        self.model.setItem(WIDGETS.W_MAX_DIST, item)
223        item = QtGui.QStandardItem("")
224        self.model.setItem(WIDGETS.W_RG, item)
225        item = QtGui.QStandardItem("")
226        self.model.setItem(WIDGETS.W_I_ZERO, item)
227        item = QtGui.QStandardItem("")
228        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, item)
229        item = QtGui.QStandardItem("")
230        self.model.setItem(WIDGETS.W_COMP_TIME, item)
231        item = QtGui.QStandardItem("")
232        self.model.setItem(WIDGETS.W_CHI_SQUARED, item)
233        item = QtGui.QStandardItem("")
234        self.model.setItem(WIDGETS.W_OSCILLATION, item)
235        item = QtGui.QStandardItem("")
236        self.model.setItem(WIDGETS.W_POS_FRACTION, item)
237        item = QtGui.QStandardItem("")
238        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, item)
239
240    ######################################################################
241    # Methods for updating GUI
242
243    def enableButtons(self):
244        """
245        Enable buttons when data is present, else disable them
246        """
247        if self.logic.data_is_loaded:
248            self.explorerButton.setEnabled(True)
249            self.calculateButton.setEnabled(True)
250        else:
251            self.calculateButton.setEnabled(False)
252            self.explorerButton.setEnabled(False)
253
254    def populateDataComboBox(self, filename):
255        """
256        Append a new file name to the data combobox
257        :param data: Data1D object
258        """
259        qt_item = QtCore.QString.fromUtf8(filename)
260        self.dataList.addItem(qt_item)
261
262    def acceptNoTerms(self):
263        """Send estimated no of terms to input"""
264        self.model.setItem(WIDGETS.W_NO_TERMS, QtGui.QStandardItem(
265            self.noOfTermsSuggestionButton.text()))
266
267    def acceptAlpha(self):
268        """Send estimated alpha to input"""
269        self.model.setItem(WIDGETS.W_REGULARIZATION, QtGui.QStandardItem(
270            self.regConstantSuggestionButton.text()))
271
272    def displayChange(self):
273        data_name = str(self.dataList.currentText())
274        # TODO: Find data ref based on file name
275        # TODO: Find way to link standardmodelitem with combobox entry
276
277    ######################################################################
278    # GUI Interaction Events
279
280    def update_calculator(self):
281        """Update all p(r) params"""
282        self._calculator.set_x(self._data_set.x)
283        self._calculator.set_y(self._data_set.y)
284        self._calculator.set_err(self._data_set.dy)
285
286    def _calculation(self):
287        """
288        Calculate the P(r) for every data set in the data list
289        """
290        for data_ref, pr in self._data_list.items():
291            self._data_set = GuiUtils.dataFromItem(data_ref)
292            self._calculator = pr
293            # Set data before running the calculations
294            self.update_calculator()
295            # Run
296            self.startThread()
297
298    def model_changed(self):
299        """Update the values when user makes changes"""
300        if not self.mapper:
301            return
302        if self.pr_plot is not None:
303            title = self.pr_plot.name
304            GuiUtils.updateModelItemWithPlot(
305                self._data, QtCore.QVariant(self.pr_plot), title)
306        if self.data_plot is not None:
307            title = self.data_plot.name
308            GuiUtils.updateModelItemWithPlot(
309                self._data, QtCore.QVariant(self.data_plot), title)
310        self.mapper.toFirst()
311
312    def help(self):
313        """
314        Open the P(r) Inversion help browser
315        """
316        tree_location = (GuiUtils.HELP_DIRECTORY_LOCATION +
317                         "user/sasgui/perspectives/pr/pr_help.html")
318
319        # Actual file anchor will depend on the combo box index
320        # Note that we can be clusmy here, since bad current_fitter_id
321        # will just make the page displayed from the top
322        self._helpView.load(QtCore.QUrl(tree_location))
323        self._helpView.show()
324
325    def toggleBgd(self):
326        """
327        Toggle the background between manual and estimated
328        :param item: gui item that was triggered
329        """
330        sender = self.sender()
331        if sender is self.estimateBgd:
332            self.backgroundInput.setEnabled(False)
333        else:
334            self.backgroundInput.setEnabled(True)
335
336    def openExplorerWindow(self):
337        """
338        Open the Explorer window to see correlations between params and results
339        """
340        # TODO: This depends on SVCC-45
341        pass
342
343    ######################################################################
344    # Response Actions
345
346    def setData(self, data_item=None, is_batch=False):
347        """
348        Assign new data set or sets to the P(r) perspective
349        Obtain a QStandardItem object and dissect it to get Data1D/2D
350        Pass it over to the calculator
351        """
352        assert data_item is not None
353
354        if not isinstance(data_item, list):
355            msg = "Incorrect type passed to the P(r) Perspective"
356            raise AttributeError, msg
357
358        if not isinstance(data_item[0], QtGui.QStandardItem):
359            msg = "Incorrect type passed to the P(r) Perspective"
360            raise AttributeError, msg
361
362        for data in data_item:
363            # Data references
364            self._data = data
365            self._data_set = GuiUtils.dataFromItem(data)
366            self.populateDataComboBox(self._data_set.filename)
367            self._data_list[self._data] = self._calculator
368
369            # Estimate initial values from data
370            self.performEstimate()
371            self.logic = InversionLogic(self._data_set)
372
373            # Estimate q range
374            qmin, qmax = self.logic.computeDataRange()
375            self.model.setItem(WIDGETS.W_QMIN, QtGui.QStandardItem(
376                "{:.4g}".format(qmin)))
377            self.model.setItem(WIDGETS.W_QMAX, QtGui.QStandardItem(
378                "{:.4g}".format(qmax)))
379
380            self.enableButtons()
381
382    ######################################################################
383    # Thread Creators
384
385    def startThread(self):
386        """
387            Start a calculation thread
388        """
389        from Thread import CalcPr
390
391        # If a thread is already started, stop it
392        if self.calc_thread is not None and self.calc_thread.isrunning():
393            self.calc_thread.stop()
394        pr = self._calculator.clone()
395        nfunc = int(UI.TabbedInversionUI._fromUtf8(
396            self.noOfTermsInput.text()))
397        self.calc_thread = CalcPr(pr, nfunc,
398                                  error_func=self._threadError,
399                                  completefn=self._completed, updatefn=None)
400        self.calc_thread.queue()
401        self.calc_thread.ready(2.5)
402
403    def performEstimateNT(self):
404        """
405            Perform parameter estimation
406        """
407        from Thread import EstimateNT
408
409        # If a thread is already started, stop it
410        if (self.estimation_thread is not None and
411                self.estimation_thread.isrunning()):
412            self.estimation_thread.stop()
413        pr = self._calculator.clone()
414        # Skip the slit settings for the estimation
415        # It slows down the application and it doesn't change the estimates
416        pr.slit_height = 0.0
417        pr.slit_width = 0.0
418        nfunc = int(UI.TabbedInversionUI._fromUtf8(
419            self.noOfTermsInput.text()))
420        self.estimation_thread = EstimateNT(pr, nfunc,
421                                            error_func=self._threadError,
422                                            completefn=self._estimateNTCompleted,
423                                            updatefn=None)
424        self.estimation_thread.queue()
425        self.estimation_thread.ready(2.5)
426
427    def performEstimate(self):
428        """
429            Perform parameter estimation
430        """
431        from Thread import EstimatePr
432
433        self._calculation()
434
435        # If a thread is already started, stop it
436        if (self.estimation_thread is not None and
437                self.estimation_thread.isrunning()):
438            self.estimation_thread.stop()
439        pr = self._calculator.clone()
440        nfunc = int(UI.TabbedInversionUI._fromUtf8(
441            self.noOfTermsInput.text()))
442        self.estimation_thread = EstimatePr(pr, nfunc,
443                                            error_func=self._threadError,
444                                            completefn=self._estimateCompleted,
445                                            updatefn=None)
446        self.estimation_thread.queue()
447        self.estimation_thread.ready(2.5)
448
449    ######################################################################
450    # Thread Complete
451
452    def _estimateCompleted(self, alpha, message, elapsed):
453        """
454        Parameter estimation completed,
455        display the results to the user
456
457        :param alpha: estimated best alpha
458        :param elapsed: computation time
459        """
460        # Save useful info
461        self.model.setItem(WIDGETS.W_COMP_TIME,
462                           QtGui.QStandardItem(str(elapsed)))
463        self.regConstantSuggestionButton.setText(QtCore.QString(str(alpha)))
464        self.regConstantSuggestionButton.setEnabled(True)
465        if message:
466            logging.info(message)
467        self.performEstimateNT()
468
469    def _estimateNTCompleted(self, nterms, alpha, message, elapsed):
470        """
471        Parameter estimation completed,
472        display the results to the user
473
474        :param alpha: estimated best alpha
475        :param nterms: estimated number of terms
476        :param elapsed: computation time
477
478        """
479        # Save useful info
480        self.noOfTermsSuggestionButton.setText(QtCore.QString(
481            "{:n}".format(nterms)))
482        self.noOfTermsSuggestionButton.setEnabled(True)
483        self.regConstantSuggestionButton.setText(QtCore.QString(
484            "{:.3g}".format(alpha)))
485        self.regConstantSuggestionButton.setEnabled(True)
486        self.model.setItem(WIDGETS.W_COMP_TIME,
487                           QtGui.QStandardItem(str(elapsed)))
488        self.PrTabWidget.setCurrentIndex(0)
489        if message:
490            logging.info(message)
491
492    def _completed(self, out, cov, pr, elapsed):
493        """
494        Method called with the results when the inversion is done
495
496        :param out: output coefficient for the base functions
497        :param cov: covariance matrix
498        :param pr: Invertor instance
499        :param elapsed: time spent computing
500
501        """
502        # Save useful info
503        cov = np.ascontiguousarray(cov)
504        pr.cov = cov
505        pr.out = out
506        pr.elapsed = elapsed
507
508        # Show result on control panel
509
510        # TODO: Connect self._calculator to GUI
511        self.model.setItem(WIDGETS.W_RG, QtGui.QStandardItem(str(pr.rg(out))))
512        self.model.setItem(WIDGETS.W_I_ZERO,
513                           QtGui.QStandardItem(str(pr.iq0(out))))
514        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT,
515                           QtGui.QStandardItem("{:.3f}".format(pr.background)))
516        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT,
517                           QtGui.QStandardItem(str(pr.background)))
518        self.model.setItem(WIDGETS.W_CHI_SQUARED,
519                           QtGui.QStandardItem(str(pr.chi2[0])))
520        self.model.setItem(WIDGETS.W_COMP_TIME,
521                           QtGui.QStandardItem(str(elapsed)))
522        self.model.setItem(WIDGETS.W_OSCILLATION,
523                           QtGui.QStandardItem(str(pr.oscillations(out))))
524        self.model.setItem(WIDGETS.W_POS_FRACTION,
525                           QtGui.QStandardItem(str(pr.get_positive(out))))
526        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION,
527                           QtGui.QStandardItem(str(pr.get_pos_err(out, cov))))
528
529        # Display results tab
530        self.PrTabWidget.setCurrentIndex(1)
531        # Save Pr invertor
532        self._calculator = pr
533        # Append data to data list
534        self._data_list[self._data] = self._calculator.clone()
535
536        if self.pr_plot is None:
537            self.pr_plot = self.logic.newPRPlot(out, self._calculator, cov)
538        if self.data_plot is None:
539            self.data_plot = self.logic.new1DPlot(out, self._calculator)
540
541    def _threadError(self, error):
542        """
543            Call-back method for calculation errors
544        """
545        logging.warning(error)
Note: See TracBrowser for help on using the repository browser.