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

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

Fixed Mdi area and widget resizing issue(s)

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