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

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

Initial unit tests for inversion - SASVIEW-609

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