source: sasview/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py @ 3b8cc00

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 3b8cc00 was e90988c, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Show help pages in default browser. Fixed some help links and modified unit tests. SASVIEW-800

  • Property mode set to 100644
File size: 30.7 KB
RevLine 
[f721030]1# global
2import sys
3import os
[f1f3e6a]4import logging
5import copy
6import webbrowser
7
[4992ff2]8from PyQt5 import QtCore
[f1f3e6a]9from PyQt5 import QtGui, QtWidgets
[f721030]10
11from twisted.internet import threads
[1042dba]12from twisted.internet import reactor
[f721030]13
14# sas-global
15from sas.sascalc.invariant import invariant
[dc5ef15]16from sas.qtgui.Plotting.PlotterData import Data1D
[83eb5208]17import sas.qtgui.Utilities.GuiUtils as GuiUtils
[f721030]18
[f1f3e6a]19# import sas.qtgui.Plotting.PlotHelper as PlotHelper
20
[f721030]21# local
[b3e8629]22from .UI.TabbedInvariantUI import Ui_tabbedInvariantUI
23from .InvariantDetails import DetailsDialog
24from .InvariantUtils import WIDGETS
[f721030]25
26# The minimum q-value to be used when extrapolating
27Q_MINIMUM = 1e-5
28# The maximum q-value to be used when extrapolating
29Q_MAXIMUM = 10
30# the ratio of maximum q value/(qmax of data) to plot the theory data
31Q_MAXIMUM_PLOT = 3
[f1f3e6a]32# Default number of points of interpolation: high and low range
33NPOINTS_Q_INTERP = 10
34# Default power law for interpolation
35DEFAULT_POWER_LOW = 4
[f721030]36
[f1f3e6a]37# Background of line edits if settings OK or wrong
38BG_WHITE = "background-color: rgb(255, 255, 255);"
39BG_RED = "background-color: rgb(244, 170, 164);"
[f721030]40
[4992ff2]41class InvariantWindow(QtWidgets.QDialog, Ui_tabbedInvariantUI):
[f721030]42    # The controller which is responsible for managing signal slots connections
43    # for the gui and providing an interface to the data model.
[f1f3e6a]44    name = "Invariant"  # For displaying in the combo box in DataExplorer
45
[0979dfb]46    def __init__(self, parent=None):
47        super(InvariantWindow, self).__init__()
[469b4622]48        self.setupUi(self)
49
[f721030]50        self.setWindowTitle("Invariant Perspective")
[e540cd2]51
[f721030]52        # initial input params
53        self._background = 0.0
54        self._scale = 1.0
55        self._contrast = 1.0
[f1f3e6a]56        self._porod = None
57
58        self.parent = parent
[f721030]59
[0979dfb]60        self._manager = parent
[6fd4e36]61        self._reactor = reactor
[a281ab8]62        self._model_item = QtGui.QStandardItem()
[f721030]63
64        self.detailsDialog = DetailsDialog(self)
[f1f3e6a]65        self.detailsDialog.cmdOK.clicked.connect(self.enabling)
[f721030]66
67        self._low_extrapolate = False
[f1f3e6a]68        self._low_guinier = True
69        self._low_fit = False
70        self._low_power_value = False
71        self._low_points = NPOINTS_Q_INTERP
72        self._low_power_value = DEFAULT_POWER_LOW
73
[f721030]74        self._high_extrapolate = False
[f1f3e6a]75        self._high_power_value = False
76        self._high_fit = False
77        self._high_points = NPOINTS_Q_INTERP
78        self._high_power_value = DEFAULT_POWER_LOW
[f721030]79
[d813cad8]80        # no reason to have this widget resizable
[d1955d67]81        self.resize(self.minimumSizeHint())
[28a84e9]82
[f1f3e6a]83        self.communicate = self._manager.communicator()
[f721030]84
[469b4622]85        self._data = None
86        self._path = ""
87
[b1e36a3]88        self._allow_close = False
89
[f1f3e6a]90        # Modify font in order to display Angstrom symbol correctly
91        new_font = 'font-family: -apple-system, "Helvetica Neue", "Ubuntu";'
92        self.lblTotalQUnits.setStyleSheet(new_font)
93        self.lblSpecificSurfaceUnits.setStyleSheet(new_font)
94        self.lblInvariantTotalQUnits.setStyleSheet(new_font)
95        self.lblContrastUnits.setStyleSheet(new_font)
96        self.lblPorodCstUnits.setStyleSheet(new_font)
97        self.lblExtrapolQUnits.setStyleSheet(new_font)
98
99        # To remove blue square around line edits
100        self.txtBackgd.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
101        self.txtContrast.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
102        self.txtScale.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
103        self.txtPorodCst.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
104        self.txtNptsHighQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
105        self.txtNptsLowQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
106        self.txtPowerLowQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
107        self.txtPowerHighQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
108
109        self.txtExtrapolQMin.setText(str(Q_MINIMUM))
110        self.txtExtrapolQMax.setText(str(Q_MAXIMUM))
[f721030]111
112        # Let's choose the Standard Item Model.
113        self.model = QtGui.QStandardItemModel(self)
114
115        # Connect buttons to slots.
116        # Needs to be done early so default values propagate properly.
117        self.setupSlots()
118
119        # Set up the model.
120        self.setupModel()
121
122        # Set up the mapper
123        self.setupMapper()
124
[7c487846]125        # Default enablement
126        self.cmdCalculate.setEnabled(False)
127
[f1f3e6a]128        # validator: double
[7c487846]129        self.txtBackgd.setValidator(GuiUtils.DoubleValidator())
130        self.txtContrast.setValidator(GuiUtils.DoubleValidator())
131        self.txtScale.setValidator(GuiUtils.DoubleValidator())
132        self.txtPorodCst.setValidator(GuiUtils.DoubleValidator())
[f1f3e6a]133
134        # validator: integer number
[7c487846]135        self.txtNptsLowQ.setValidator(QtGui.QIntValidator())
136        self.txtNptsHighQ.setValidator(QtGui.QIntValidator())
137        self.txtPowerLowQ.setValidator(GuiUtils.DoubleValidator())
138        self.txtPowerHighQ.setValidator(GuiUtils.DoubleValidator())
[f1f3e6a]139
140    def enabling(self):
141        """ """
142        self.cmdStatus.setEnabled(True)
[457d961]143
[b1e36a3]144    def setClosable(self, value=True):
[f1f3e6a]145        """ Allow outsiders close this widget """
[b1e36a3]146        assert isinstance(value, bool)
147
148        self._allow_close = value
149
150    def closeEvent(self, event):
151        """
152        Overwrite QDialog close method to allow for custom widget close
153        """
154        if self._allow_close:
155            # reset the closability flag
156            self.setClosable(value=False)
[7c487846]157            # Tell the MdiArea to close the container
158            self.parentWidget().close()
[b1e36a3]159            event.accept()
160        else:
161            event.ignore()
162            # Maybe we should just minimize
163            self.setWindowState(QtCore.Qt.WindowMinimized)
[e540cd2]164
[f721030]165    def updateFromModel(self):
[f1f3e6a]166        """ Update the globals based on the data in the model """
[f721030]167        self._background = float(self.model.item(WIDGETS.W_BACKGROUND).text())
[f1f3e6a]168        self._contrast = float(self.model.item(WIDGETS.W_CONTRAST).text())
169        self._scale = float(self.model.item(WIDGETS.W_SCALE).text())
170        if self.model.item(WIDGETS.W_POROD_CST).text() != 'None' \
171                and self.model.item(WIDGETS.W_POROD_CST).text() != '':
172            self._porod = float(self.model.item(WIDGETS.W_POROD_CST).text())
173
174        # Low extrapolating
175        self._low_extrapolate = str(self.model.item(WIDGETS.W_ENABLE_LOWQ).text()) == 'true'
[f721030]176        self._low_points = float(self.model.item(WIDGETS.W_NPTS_LOWQ).text())
[f1f3e6a]177        self._low_guinier = str(self.model.item(WIDGETS.W_LOWQ_GUINIER).text()) == 'true'
178        self._low_fit = str(self.model.item(WIDGETS.W_LOWQ_FIT).text()) == 'true'
179        self._low_power_value = float(self.model.item(WIDGETS.W_LOWQ_POWER_VALUE).text())
[f721030]180
181        # High extrapolating
[f1f3e6a]182        self._high_extrapolate = str(self.model.item(WIDGETS.W_ENABLE_HIGHQ).text()) == 'true'
183        self._high_points = float(self.model.item(WIDGETS.W_NPTS_HIGHQ).text())
184        self._high_fit = str(self.model.item(WIDGETS.W_HIGHQ_FIT).text()) == 'true'
185        self._high_power_value = float(self.model.item(WIDGETS.W_HIGHQ_POWER_VALUE).text())
[f721030]186
[a281ab8]187    def calculateInvariant(self):
[f1f3e6a]188        """ Use twisted to thread the calculations away """
[f721030]189        # Find out if extrapolation needs to be used.
190        extrapolation = None
[f1f3e6a]191        if self._low_extrapolate and not self._high_extrapolate:
[f721030]192            extrapolation = "low"
[f1f3e6a]193        elif not self._low_extrapolate and self._high_extrapolate:
[f721030]194            extrapolation = "high"
195        elif self._low_extrapolate and self._high_extrapolate:
196            extrapolation = "both"
197
[f1f3e6a]198        # modify the Calculate button to indicate background process
199        self.cmdCalculate.setText("Calculating...")
200        self.cmdCalculate.setEnabled(False)
201
202        # Send the calculations to separate thread.
203        d = threads.deferToThread(self.calculateThread, extrapolation)
204
205        # Add deferred callback for call return
206        d.addCallback(self.deferredPlot)
207        d.addErrback(self.calculationFailed)
208
209    def calculationFailed(self, reason):
210        print("calculation failed: ", reason)
[7fb471d]211        pass
[5032ea68]212
[f1f3e6a]213    def deferredPlot(self, model):
[f721030]214        """
[f1f3e6a]215        Run the GUI/model update in the main thread
[f721030]216        """
[f1f3e6a]217        reactor.callFromThread(lambda: self.plotResult(model))
[f721030]218
[f1f3e6a]219    def plotResult(self, model):
220        """ Plot result of calculation """
[f721030]221        # Set the button back to available
[f1f3e6a]222        self.cmdCalculate.setEnabled(True)
223        self.cmdCalculate.setText("Calculate")
224        self.cmdStatus.setEnabled(True)
225
226        self.model = model
[9387fe3]227        self.mapper.toFirst()
[f1f3e6a]228        self._data = GuiUtils.dataFromItem(self._model_item)
[f721030]229
[a281ab8]230        # Send the modified model item to DE for keeping in the model
[f1f3e6a]231        # Currently -unused
[6cb305a]232        # self.communicate.updateModelFromPerspectiveSignal.emit(self._model_item)
[f1f3e6a]233
234        plot_data = GuiUtils.plotsFromCheckedItems(self._manager.filesWidget.model)
235
236        self._manager.filesWidget.plotData(plot_data)
[5032ea68]237
[f1f3e6a]238        # Update the details dialog in case it is open
239        self.updateDetailsWidget(model)
240
241    def updateDetailsWidget(self, model):
242        """
243        On demand update of the details widget
244        """
245        if self.detailsDialog.isVisible():
246            self.onStatus()
[f721030]247
248    def calculateThread(self, extrapolation):
249        """
[5032ea68]250        Perform Invariant calculations.
251        TODO: Create a dictionary of results to be sent to DE on completion.
[f721030]252        """
253        self.updateFromModel()
[f1f3e6a]254        msg = ''
[f721030]255
256        qstar_low = 0.0
257        qstar_low_err = 0.0
258        qstar_high = 0.0
259        qstar_high_err = 0.0
260        qstar_total = 0.0
[f1f3e6a]261        qstar_total_error = 0.0
[f721030]262
[f1f3e6a]263        temp_data = copy.deepcopy(self._data)
[f721030]264
[f1f3e6a]265        # Prepare the invariant object
266        inv = invariant.InvariantCalculator(data=temp_data,
267                                            background=self._background,
268                                            scale=self._scale)
[f721030]269        if self._low_extrapolate:
[f1f3e6a]270
[f721030]271            function_low = "power_law"
272            if self._low_guinier:
273                function_low = "guinier"
274            if self._low_fit:
275                self._low_power_value = None
[f1f3e6a]276
[f721030]277            inv.set_extrapolation(range="low",
[f1f3e6a]278                                  npts=int(self._low_points),
[f721030]279                                  function=function_low,
280                                  power=self._low_power_value)
281
282        if self._high_extrapolate:
283            function_low = "power_law"
284            inv.set_extrapolation(range="high",
[f1f3e6a]285                                  npts=int(self._high_points),
[f721030]286                                  function=function_low,
[f1f3e6a]287                                  power=self._high_power_value)
288        # Compute invariant
[f721030]289        calculation_failed = False
[f1f3e6a]290
[f721030]291        try:
[f1f3e6a]292            qstar_total, qstar_total_error = inv.get_qstar_with_error()
[f721030]293        except Exception as ex:
[f1f3e6a]294            msg += str(ex)
[f721030]295            calculation_failed = True
296            # Display relevant information
297            item = QtGui.QStandardItem("ERROR")
298            self.model.setItem(WIDGETS.W_INVARIANT, item)
299            item = QtGui.QStandardItem("ERROR")
300            self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
[f1f3e6a]301
[f721030]302        try:
303            volume_fraction, volume_fraction_error = \
304                inv.get_volume_fraction_with_error(self._contrast)
[f1f3e6a]305
[f721030]306        except Exception as ex:
307            calculation_failed = True
[f1f3e6a]308            msg += str(ex)
[f721030]309            # Display relevant information
310            item = QtGui.QStandardItem("ERROR")
311            self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
312            item = QtGui.QStandardItem("ERROR")
313            self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
314
[f1f3e6a]315        if self._porod:
316            try:
317                surface, surface_error = \
318                    inv.get_surface_with_error(self._contrast, self._porod)
319            except Exception as ex:
320                calculation_failed = True
321                msg += str(ex)
322                # Display relevant information
323                item = QtGui.QStandardItem("ERROR")
324                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
325                item = QtGui.QStandardItem("ERROR")
326                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
327        else:
328            surface = None
329
330        if (calculation_failed):
331            logging.warning('Calculation failed: {}'.format(msg))
[f721030]332            return self.model
[f1f3e6a]333        else:
[f721030]334
[f1f3e6a]335            if self._low_extrapolate:
336                # for presentation in InvariantDetails
337                qstar_low, qstar_low_err = inv.get_qstar_low()
338                extrapolated_data = inv.get_extra_data_low(self._low_points)
339                power_low = inv.get_extrapolation_power(range='low')
340
341                # Plot the chart
342                title = "Low-Q extrapolation"
343
344                # Convert the data into plottable
345                extrapolated_data = self._manager.createGuiData(extrapolated_data)
346
347                extrapolated_data.name = title
348                extrapolated_data.title = title
349
350                # copy labels and units of axes for plotting
351                extrapolated_data._xaxis = temp_data._xaxis
352                extrapolated_data._xunit = temp_data._xunit
353                extrapolated_data._yaxis = temp_data._yaxis
354                extrapolated_data._yunit = temp_data._yunit
355
356                # Add the plot to the model item
357                # This needs to run in the main thread
358                reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
359                                       self._model_item,
360                                       extrapolated_data,
361                                       title)
362
363            if self._high_extrapolate:
364                # for presentation in InvariantDetails
[6cb305a]365                qmax_plot = Q_MAXIMUM_PLOT * max(temp_data.x)
[f1f3e6a]366
367                if qmax_plot > Q_MAXIMUM:
368                    qmax_plot = Q_MAXIMUM
369                qstar_high, qstar_high_err = inv.get_qstar_high()
370                power_high = inv.get_extrapolation_power(range='high')
371                high_out_data = inv.get_extra_data_high(q_end=qmax_plot, npts=500)
372
373                # Plot the chart
374                title = "High-Q extrapolation"
375
376                # Convert the data into plottable
377                high_out_data = self._manager.createGuiData(high_out_data)
378                high_out_data.name = title
379                high_out_data.title = title
380
381                # copy labels and units of axes for plotting
382                high_out_data._xaxis = temp_data._xaxis
383                high_out_data._xunit = temp_data._xunit
384                high_out_data._yaxis = temp_data._yaxis
385                high_out_data._yunit = temp_data._yunit
386
387                # Add the plot to the model item
388                # This needs to run in the main thread
389                reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
390                                       self._model_item, high_out_data, title)
391
392            item = QtGui.QStandardItem(str(float('%.3g'% volume_fraction)))
393            self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
394            item = QtGui.QStandardItem(str(float('%.3g'% volume_fraction_error)))
395            self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
396            if surface:
397                item = QtGui.QStandardItem(str(float('%.3g'% surface)))
398                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
399                item = QtGui.QStandardItem(str(float('%.3g'% surface_error)))
400                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
401            item = QtGui.QStandardItem(str(float('%.3g'% qstar_total)))
402            self.model.setItem(WIDGETS.W_INVARIANT, item)
403            item = QtGui.QStandardItem(str(float('%.3g'% qstar_total_error)))
404            self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
[1042dba]405
[f1f3e6a]406            item = QtGui.QStandardItem(str(float('%.3g'% qstar_low)))
407            self.model.setItem(WIDGETS.D_LOW_QSTAR, item)
408            item = QtGui.QStandardItem(str(float('%.3g'% qstar_low_err)))
409            self.model.setItem(WIDGETS.D_LOW_QSTAR_ERR, item)
410            item = QtGui.QStandardItem(str(float('%.3g'% qstar_high)))
411            self.model.setItem(WIDGETS.D_HIGH_QSTAR, item)
412            item = QtGui.QStandardItem(str(float('%.3g'% qstar_high_err)))
413            self.model.setItem(WIDGETS.D_HIGH_QSTAR_ERR, item)
[f721030]414
[f1f3e6a]415            return self.model
[f721030]416
[5032ea68]417    def title(self):
[f1f3e6a]418        """ Perspective name """
[5032ea68]419        return "Invariant panel"
[f721030]420
[f1f3e6a]421    def onStatus(self):
[f721030]422        """
[f1f3e6a]423        Display Invariant Details panel when clicking on Status button
[f721030]424        """
425        self.detailsDialog.setModel(self.model)
426        self.detailsDialog.showDialog()
[f1f3e6a]427        self.cmdStatus.setEnabled(False)
[f721030]428
[f1f3e6a]429    def onHelp(self):
430        """ Display help when clicking on Help button """
[e90988c]431        treeLocation = "/user/sasgui/perspectives/invariant/invariant_help.html"
432        self.parent.showHelp(treeLocation)
[f721030]433
434    def setupSlots(self):
[f1f3e6a]435        """ """
436        self.cmdCalculate.clicked.connect(self.calculateInvariant)
437        self.cmdStatus.clicked.connect(self.onStatus)
438        self.cmdHelp.clicked.connect(self.onHelp)
[f721030]439
[f1f3e6a]440        self.chkLowQ.stateChanged.connect(self.stateChanged)
441        self.chkLowQ.stateChanged.connect(self.checkQExtrapolatedData)
[f721030]442
[f1f3e6a]443        self.chkHighQ.stateChanged.connect(self.stateChanged)
444        self.chkHighQ.stateChanged.connect(self.checkQExtrapolatedData)
[f721030]445
[f1f3e6a]446        # slots for the Guinier and PowerLaw radio buttons at low Q
447        # since they are not auto-exclusive
448        self.rbGuinier.toggled.connect(self.lowGuinierAndPowerToggle)
449
450        self.rbPowerLawLowQ.toggled.connect(self.lowGuinierAndPowerToggle)
451
452        self.rbFitHighQ.toggled.connect(self.hiFitAndFixToggle)
453
454        self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
[f721030]455
456        self.model.itemChanged.connect(self.modelChanged)
457
[f1f3e6a]458        # update model from gui editing by users
459        self.txtBackgd.textChanged.connect(self.updateFromGui)
460
461        self.txtScale.textChanged.connect(self.updateFromGui)
462
463        self.txtContrast.textChanged.connect(self.updateFromGui)
464
465        self.txtPorodCst.textChanged.connect(self.updateFromGui)
466
467        self.txtPowerLowQ.textChanged.connect(self.updateFromGui)
468
469        self.txtPowerHighQ.textChanged.connect(self.updateFromGui)
470
471        self.txtNptsLowQ.textChanged.connect(self.updateFromGui)
472
473        self.txtNptsHighQ.textChanged.connect(self.updateFromGui)
474
475        # check values of n_points compared to distribution length
476        if self.txtNptsLowQ.isEnabled():
477            self.txtNptsLowQ.textChanged.connect(self.checkLength)
478
479        if self.txtNptsHighQ.isEnabled():
480            self.txtNptsHighQ.textChanged.connect(self.checkLength)
481
482    def stateChanged(self):
[f721030]483        """
[f1f3e6a]484        Catch modifications from low- and high-Q extrapolation check boxes
[f721030]485        """
[f1f3e6a]486        sender = self.sender()
487
488        itemf = QtGui.QStandardItem(str(sender.isChecked()).lower())
489        if sender.text() == 'Enable Low-Q extrapolation':
490            self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
491
492        if sender.text() == 'Enable High-Q extrapolation':
493            self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
494
495    def checkLength(self):
496        """
497        Validators of number of points for extrapolation.
498        Error if it is larger than the distribution length
499        """
[7c487846]500        try:
501            int_value = int(self.sender().text())
502        except ValueError:
503            self.sender().setStyleSheet(BG_RED)
504            self.cmdCalculate.setEnabled(False)
505            return
506
[f1f3e6a]507        if self._data:
[7c487846]508            if len(self._data.x) < int_value:
509                self.sender().setStyleSheet(BG_RED)
[f1f3e6a]510                logging.warning('The number of points must be smaller than {}'.format(len(self._data.x)))
511                self.cmdCalculate.setEnabled(False)
512            else:
[7c487846]513                self.sender().setStyleSheet(BG_WHITE)
[f1f3e6a]514                self.cmdCalculate.setEnabled(True)
515        else:
516            # logging.info('no data is loaded')
517            self.cmdCalculate.setEnabled(False)
518
519    def modelChanged(self, item):
520        """ Update when model changed """
[f721030]521        if item.row() == WIDGETS.W_ENABLE_LOWQ:
522            toggle = (str(item.text()) == 'true')
523            self._low_extrapolate = toggle
524            self.lowQToggle(toggle)
525        elif item.row() == WIDGETS.W_ENABLE_HIGHQ:
526            toggle = (str(item.text()) == 'true')
527            self._high_extrapolate = toggle
528            self.highQToggle(toggle)
[f1f3e6a]529
530    def checkQExtrapolatedData(self):
[f721030]531        """
[f1f3e6a]532        Match status of low or high-Q extrapolated data checkbox in
533        DataExplorer with low or high-Q extrapolation checkbox in invariant
534        panel
[f721030]535        """
[f1f3e6a]536        # name to search in DataExplorer
537        if 'Low' in str(self.sender().text()):
538            name = "Low-Q extrapolation"
539        if 'High' in str(self.sender().text()):
540            name = "High-Q extrapolation"
[f721030]541
[f1f3e6a]542        GuiUtils.updateModelItemStatus(self._manager.filesWidget.model,
543                                       self._path, name,
544                                       self.sender().checkState())
545
546    def updateFromGui(self):
547        """ Update model when new user inputs """
548        possible_senders = ['txtBackgd', 'txtContrast', 'txtPorodCst',
549                            'txtScale', 'txtPowerLowQ', 'txtPowerHighQ',
550                            'txtNptsLowQ', 'txtNptsHighQ']
551
552        related_widgets = [WIDGETS.W_BACKGROUND, WIDGETS.W_CONTRAST,
553                           WIDGETS.W_POROD_CST, WIDGETS.W_SCALE,
554                           WIDGETS.W_LOWQ_POWER_VALUE, WIDGETS.W_HIGHQ_POWER_VALUE,
555                           WIDGETS.W_NPTS_LOWQ, WIDGETS.W_NPTS_HIGHQ]
556
557        related_internal_values = [self._background, self._contrast,
558                                   self._porod, self._scale,
559                                   self._low_power_value,
560                                   self._high_power_value,
561                                   self._low_points, self._high_points]
562
563        item = QtGui.QStandardItem(self.sender().text())
564
565        index_elt = possible_senders.index(self.sender().objectName())
566
567        self.model.setItem(related_widgets[index_elt], item)
[7c487846]568        try:
569            related_internal_values[index_elt] = float(self.sender().text())
570            self.sender().setStyleSheet(BG_WHITE)
571            self.cmdCalculate.setEnabled(True)
572        except ValueError:
573            # empty field, just skip
574            self.sender().setStyleSheet(BG_RED)
575            self.cmdCalculate.setEnabled(False)
[f1f3e6a]576
577    def lowGuinierAndPowerToggle(self, toggle):
[f721030]578        """
[f1f3e6a]579        Guinier and Power radio buttons cannot be selected at the same time
580        If Power is selected, Fit and Fix radio buttons are visible and
581        Power line edit can be edited if Fix is selected
[f721030]582        """
[f1f3e6a]583        if self.sender().text() == 'Guinier':
584            self._low_guinier = toggle
585
586            toggle = not toggle
587            self.rbPowerLawLowQ.setChecked(toggle)
588
589            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
590            self.rbFitLowQ.setVisible(toggle)
591            self.rbFixLowQ.setVisible(toggle)
592
593            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
594
595        else:
596            self._low_guinier = not toggle
597
598            self.rbGuinier.setChecked(not toggle)
599
600            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
601            self.rbFitLowQ.setVisible(toggle)
602            self.rbFixLowQ.setVisible(toggle)
603
604            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
605
606    def lowFitAndFixToggle(self, toggle):
607        """ Fit and Fix radiobuttons cannot be selected at the same time """
[f721030]608        self._low_fit = toggle
[f1f3e6a]609
[f721030]610        toggle = not toggle
[f1f3e6a]611        self.txtPowerLowQ.setEnabled(toggle)
[f721030]612
613    def hiFitAndFixToggle(self, toggle):
614        """
[f1f3e6a]615        Enable editing of power exponent if Fix for high Q is checked
616        Disable otherwise
[f721030]617        """
[f1f3e6a]618        self.txtPowerHighQ.setEnabled(not toggle)
[f721030]619
620    def highQToggle(self, clicked):
[f1f3e6a]621        """ Disable/enable High Q extrapolation """
622        self.rbFitHighQ.setEnabled(clicked)
623        self.rbFixHighQ.setEnabled(clicked)
624        self.txtNptsHighQ.setEnabled(clicked)
625        self.txtPowerHighQ.setEnabled(clicked)
[f721030]626
627    def lowQToggle(self, clicked):
[f1f3e6a]628        """ Disable / enable Low Q extrapolation """
629        self.rbGuinier.setEnabled(clicked)
630        self.rbPowerLawLowQ.setEnabled(clicked)
631        self.txtNptsLowQ.setEnabled(clicked)
[f721030]632        # Enable subelements
[f1f3e6a]633        self.rbFitLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
634        self.rbFixLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
635        self.rbFitLowQ.setEnabled(clicked)  # and not self._low_guinier)
636        self.rbFixLowQ.setEnabled(clicked)  # and not self._low_guinier)
[f721030]637
[f1f3e6a]638        self.txtPowerLowQ.setEnabled(clicked
639                                    and not self._low_guinier
640                                    and not self._low_fit)
[f721030]641
[f1f3e6a]642    def setupModel(self):
643        """ """
[f721030]644        # filename
645        item = QtGui.QStandardItem(self._path)
646        self.model.setItem(WIDGETS.W_FILENAME, item)
647
648        # add Q parameters to the model
[469b4622]649        qmin = 0.0
[f721030]650        item = QtGui.QStandardItem(str(qmin))
651        self.model.setItem(WIDGETS.W_QMIN, item)
[469b4622]652        qmax = 0.0
653        item = QtGui.QStandardItem(str(qmax))
[f721030]654        self.model.setItem(WIDGETS.W_QMAX, item)
655
656        # add custom input params
657        item = QtGui.QStandardItem(str(self._background))
658        self.model.setItem(WIDGETS.W_BACKGROUND, item)
659        item = QtGui.QStandardItem(str(self._contrast))
660        self.model.setItem(WIDGETS.W_CONTRAST, item)
661        item = QtGui.QStandardItem(str(self._scale))
662        self.model.setItem(WIDGETS.W_SCALE, item)
[f1f3e6a]663        # leave line edit empty if Porod constant not defined
664        if self._porod != None:
665            item = QtGui.QStandardItem(str(self._porod))
666        else:
667            item = QtGui.QStandardItem(str(''))
668        self.model.setItem(WIDGETS.W_POROD_CST, item)
669
[f721030]670        # Dialog elements
671        itemf = QtGui.QStandardItem("false")
672        self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
673        itemf = QtGui.QStandardItem("false")
674        self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
675
[f1f3e6a]676        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
[f721030]677        self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
[f1f3e6a]678        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
[f721030]679        self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
680
681        itemt = QtGui.QStandardItem("true")
682        self.model.setItem(WIDGETS.W_LOWQ_GUINIER, itemt)
683
684        itemt = QtGui.QStandardItem("true")
685        self.model.setItem(WIDGETS.W_LOWQ_FIT, itemt)
[f1f3e6a]686        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
[f721030]687        self.model.setItem(WIDGETS.W_LOWQ_POWER_VALUE, item)
688
689        itemt = QtGui.QStandardItem("true")
690        self.model.setItem(WIDGETS.W_HIGHQ_FIT, itemt)
[f1f3e6a]691        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
[f721030]692        self.model.setItem(WIDGETS.W_HIGHQ_POWER_VALUE, item)
693
694    def setupMapper(self):
695        # Set up the mapper.
[4992ff2]696        self.mapper = QtWidgets.QDataWidgetMapper(self)
[f721030]697        self.mapper.setOrientation(QtCore.Qt.Vertical)
698        self.mapper.setModel(self.model)
699
700        # Filename
[f1f3e6a]701        self.mapper.addMapping(self.txtName, WIDGETS.W_FILENAME)
702
[f721030]703        # Qmin/Qmax
[f1f3e6a]704        self.mapper.addMapping(self.txtTotalQMin, WIDGETS.W_QMIN)
705        self.mapper.addMapping(self.txtTotalQMax, WIDGETS.W_QMAX)
[f721030]706
707        # Background
[f1f3e6a]708        self.mapper.addMapping(self.txtBackgd, WIDGETS.W_BACKGROUND)
709
[f721030]710        # Scale
[f1f3e6a]711        self.mapper.addMapping(self.txtScale, WIDGETS.W_SCALE)
712
[f721030]713        # Contrast
[f1f3e6a]714        self.mapper.addMapping(self.txtContrast, WIDGETS.W_CONTRAST)
[f721030]715
[f1f3e6a]716        # Porod constant
717        self.mapper.addMapping(self.txtPorodCst, WIDGETS.W_POROD_CST)
[f721030]718
[f1f3e6a]719        # Lowq/Highq items
720        self.mapper.addMapping(self.chkLowQ, WIDGETS.W_ENABLE_LOWQ)
721        self.mapper.addMapping(self.chkHighQ, WIDGETS.W_ENABLE_HIGHQ)
[f721030]722
[f1f3e6a]723        self.mapper.addMapping(self.txtNptsLowQ, WIDGETS.W_NPTS_LOWQ)
724        self.mapper.addMapping(self.rbGuinier, WIDGETS.W_LOWQ_GUINIER)
725        self.mapper.addMapping(self.rbFitLowQ, WIDGETS.W_LOWQ_FIT)
726        self.mapper.addMapping(self.txtPowerLowQ, WIDGETS.W_LOWQ_POWER_VALUE)
[f721030]727
[f1f3e6a]728        self.mapper.addMapping(self.txtNptsHighQ, WIDGETS.W_NPTS_HIGHQ)
729        self.mapper.addMapping(self.rbFitHighQ, WIDGETS.W_HIGHQ_FIT)
730        self.mapper.addMapping(self.txtPowerHighQ, WIDGETS.W_HIGHQ_POWER_VALUE)
[f721030]731   
732        # Output
[f1f3e6a]733        self.mapper.addMapping(self.txtVolFract, WIDGETS.W_VOLUME_FRACTION)
734        self.mapper.addMapping(self.txtVolFractErr, WIDGETS.W_VOLUME_FRACTION_ERR)
735        self.mapper.addMapping(self.txtSpecSurf, WIDGETS.W_SPECIFIC_SURFACE)
736        self.mapper.addMapping(self.txtSpecSurfErr, WIDGETS.W_SPECIFIC_SURFACE_ERR)
737        self.mapper.addMapping(self.txtInvariantTot, WIDGETS.W_INVARIANT)
738        self.mapper.addMapping(self.txtInvariantTotErr, WIDGETS.W_INVARIANT_ERR)
[f721030]739
[f1f3e6a]740        self.mapper.toFirst()
[f721030]741
[f1f3e6a]742    def setData(self, data_item=None, is_batch=False):
[a281ab8]743        """
744        Obtain a QStandardItem object and dissect it to get Data1D/2D
745        Pass it over to the calculator
746        """
[f1f3e6a]747        assert data_item is not None
748
749        if self.txtName.text() == data_item[0].text():
750            logging.info('This file is already loaded in Invariant panel.')
751            return
752
[a281ab8]753        if not isinstance(data_item, list):
754            msg = "Incorrect type passed to the Invariant Perspective"
[b3e8629]755            raise AttributeError(msg)
[a281ab8]756
757        if not isinstance(data_item[0], QtGui.QStandardItem):
758            msg = "Incorrect type passed to the Invariant Perspective"
[b3e8629]759            raise AttributeError(msg)
[a281ab8]760
[f1f3e6a]761        # only 1 file can be loaded
[a281ab8]762        self._model_item = data_item[0]
763
764        # Extract data on 1st child - this is the Data1D/2D component
[8548d739]765        data = GuiUtils.dataFromItem(self._model_item)
[7c487846]766        self.model.item(WIDGETS.W_FILENAME).setData(self._model_item.text())
[f1f3e6a]767        # update GUI and model with info from loaded data
768        self.updateGuiFromFile(data=data)
[a281ab8]769
[f1f3e6a]770    def updateGuiFromFile(self, data=None):
[f721030]771        """
[f1f3e6a]772        update display in GUI and plot
[f721030]773        """
[f1f3e6a]774        self._data = data
775
776        # plot loaded file
777        if not isinstance(self._data, Data1D):
778            msg = "Error(s) occurred: Invariant cannot be computed with 2D data."
779            raise AttributeError(msg)
780
781        try:
782            filename = data.filename
783        except:
784            msg = 'No filename'
785            raise ValueError(msg)
786        try:
787            qmin = min(self._data.x)
788            qmax = max(self._data.x)
789        except:
790            msg = "Unable to find q min/max of \n data named %s" % \
791                  data.filename
792            raise ValueError(msg)
793
794        # update model with input form files: filename, qmin, qmax
795        self.model.item(WIDGETS.W_FILENAME).setText(filename)
796        self.model.item(WIDGETS.W_QMIN).setText(str(qmin))
797        self.model.item(WIDGETS.W_QMAX).setText(str(qmax))
798        self._path = filename
799
800        # Calculate and add to GUI: volume fraction, invariant total,
801        # and specific surface if porod checked
802        self.calculateInvariant()
[f721030]803
[5032ea68]804    def allowBatch(self):
805        """
806        Tell the caller that we don't accept multiple data instances
807        """
808        return False
Note: See TracBrowser for help on using the repository browser.