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

Last change on this file since 033b1f2 was 4cdfa17, checked in by wojciech, 6 years ago

Moving qt parts to main thread in Invariant perspective

  • Property mode set to 100644
File size: 31.2 KB
Line 
1# global
2import sys
3import os
4import logging
5import copy
6import webbrowser
7
8from PyQt5 import QtCore
9from PyQt5 import QtGui, QtWidgets
10
11from twisted.internet import threads
12from twisted.internet import reactor
13
14# sas-global
15from sas.sascalc.invariant import invariant
16from sas.qtgui.Plotting.PlotterData import Data1D
17import sas.qtgui.Utilities.GuiUtils as GuiUtils
18
19# import sas.qtgui.Plotting.PlotHelper as PlotHelper
20
21# local
22from .UI.TabbedInvariantUI import Ui_tabbedInvariantUI
23from .InvariantDetails import DetailsDialog
24from .InvariantUtils import WIDGETS
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
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
36
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);"
40
41class InvariantWindow(QtWidgets.QDialog, Ui_tabbedInvariantUI):
42    # The controller which is responsible for managing signal slots connections
43    # for the gui and providing an interface to the data model.
44    name = "Invariant"  # For displaying in the combo box in DataExplorer
45
46    def __init__(self, parent=None):
47        super(InvariantWindow, self).__init__()
48        self.setupUi(self)
49
50        self.setWindowTitle("Invariant Perspective")
51
52        # initial input params
53        self._background = 0.0
54        self._scale = 1.0
55        self._contrast = 1.0
56        self._porod = None
57
58        self.parent = parent
59
60        self._manager = parent
61        self._reactor = reactor
62        self._model_item = QtGui.QStandardItem()
63
64        self.detailsDialog = DetailsDialog(self)
65        self.detailsDialog.cmdOK.clicked.connect(self.enabling)
66
67        self._low_extrapolate = False
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
74        self._high_extrapolate = False
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
79
80        # no reason to have this widget resizable
81        self.resize(self.minimumSizeHint())
82
83        self.communicate = self._manager.communicator()
84
85        self._data = None
86        self._path = ""
87
88        self._allow_close = False
89
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))
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
125        # Default enablement
126        self.cmdCalculate.setEnabled(False)
127
128        # validator: double
129        self.txtBackgd.setValidator(GuiUtils.DoubleValidator())
130        self.txtContrast.setValidator(GuiUtils.DoubleValidator())
131        self.txtScale.setValidator(GuiUtils.DoubleValidator())
132        self.txtPorodCst.setValidator(GuiUtils.DoubleValidator())
133
134        # validator: integer number
135        self.txtNptsLowQ.setValidator(QtGui.QIntValidator())
136        self.txtNptsHighQ.setValidator(QtGui.QIntValidator())
137        self.txtPowerLowQ.setValidator(GuiUtils.DoubleValidator())
138        self.txtPowerHighQ.setValidator(GuiUtils.DoubleValidator())
139
140    def enabling(self):
141        """ """
142        self.cmdStatus.setEnabled(True)
143
144    def setClosable(self, value=True):
145        """ Allow outsiders close this widget """
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)
157            # Tell the MdiArea to close the container
158            self.parentWidget().close()
159            event.accept()
160        else:
161            event.ignore()
162            # Maybe we should just minimize
163            self.setWindowState(QtCore.Qt.WindowMinimized)
164
165    def updateFromModel(self):
166        """ Update the globals based on the data in the model """
167        self._background = float(self.model.item(WIDGETS.W_BACKGROUND).text())
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'
176        self._low_points = float(self.model.item(WIDGETS.W_NPTS_LOWQ).text())
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())
180
181        # High extrapolating
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())
186
187    def calculateInvariant(self):
188        """ Use twisted to thread the calculations away """
189        # Find out if extrapolation needs to be used.
190        extrapolation = None
191        if self._low_extrapolate and not self._high_extrapolate:
192            extrapolation = "low"
193        elif not self._low_extrapolate and self._high_extrapolate:
194            extrapolation = "high"
195        elif self._low_extrapolate and self._high_extrapolate:
196            extrapolation = "both"
197
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)
211        pass
212
213    def deferredPlot(self, model):
214        """
215        Run the GUI/model update in the main thread
216        """
217        reactor.callFromThread(lambda: self.plotResult(model))
218
219    def plotResult(self, model):
220        """ Plot result of calculation """
221        # Set the button back to available
222        self.cmdCalculate.setEnabled(True)
223        self.cmdCalculate.setText("Calculate")
224        self.cmdStatus.setEnabled(True)
225
226        self.model = model
227        self.mapper.toFirst()
228        self._data = GuiUtils.dataFromItem(self._model_item)
229
230        # Send the modified model item to DE for keeping in the model
231        # Currently -unused
232        # self.communicate.updateModelFromPerspectiveSignal.emit(self._model_item)
233
234        plot_data = GuiUtils.plotsFromCheckedItems(self._manager.filesWidget.model)
235        #self.communicate.plotRequestedSignal.emit([item, plot], self.tab_id)
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
335        low_calculation_pass = True
336        high_calculation_pass = True
337        if self._low_extrapolate:
338            try:
339                qstar_low, qstar_low_err = inv.get_qstar_low()
340            except Exception as ex:
341                low_calculation_pass = False
342                msg += str(ex)
343                logging.warning('Low-q calculation failed: {}'.format(msg))
344        if self._high_extrapolate:
345            try:
346                qstar_high, qstar_high_err = inv.get_qstar_high()
347            except Exception as ex:
348                high_calculation_pass = False
349                msg += str(ex)
350                logging.warning('High-q calculation failed: {}'.format(msg))
351
352
353        if self._low_extrapolate and low_calculation_pass:
354            extrapolated_data = inv.get_extra_data_low(self._low_points)
355            power_low = inv.get_extrapolation_power(range='low')
356
357            # Plot the chart
358            title = "Low-Q extrapolation"
359
360            # Convert the data into plottable
361            extrapolated_data = self._manager.createGuiData(extrapolated_data)
362
363            extrapolated_data.name = title
364            extrapolated_data.title = title
365            extrapolated_data.symbol = "Line"
366            extrapolated_data.has_errors = False
367
368            # copy labels and units of axes for plotting
369            extrapolated_data._xaxis = temp_data._xaxis
370            extrapolated_data._xunit = temp_data._xunit
371            extrapolated_data._yaxis = temp_data._yaxis
372            extrapolated_data._yunit = temp_data._yunit
373
374            # Add the plot to the model item
375            # This needs to run in the main thread
376            reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
377                                       self._model_item,
378                                       extrapolated_data,
379                                       title)
380
381        if self._high_extrapolate and high_calculation_pass:
382            # for presentation in InvariantDetails
383            qmax_plot = Q_MAXIMUM_PLOT * max(temp_data.x)
384
385            if qmax_plot > Q_MAXIMUM:
386                qmax_plot = Q_MAXIMUM
387            power_high = inv.get_extrapolation_power(range='high')
388            high_out_data = inv.get_extra_data_high(q_end=qmax_plot, npts=500)
389
390            # Plot the chart
391            title = "High-Q extrapolation"
392
393            # Convert the data into plottable
394            high_out_data = self._manager.createGuiData(high_out_data)
395            high_out_data.name = title
396            high_out_data.title = title
397            high_out_data.symbol = "Line"
398            high_out_data.has_errors = False
399
400            # copy labels and units of axes for plotting
401            high_out_data._xaxis = temp_data._xaxis
402            high_out_data._xunit = temp_data._xunit
403            high_out_data._yaxis = temp_data._yaxis
404            high_out_data._yunit = temp_data._yunit
405
406            # Add the plot to the model item
407            # This needs to run in the main thread
408            reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
409                                       self._model_item, high_out_data, title)
410
411        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_VOLUME_FRACTION, volume_fraction)
412        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_VOLUME_FRACTION_ERR, volume_fraction_error)
413        if surface:
414            reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_SPECIFIC_SURFACE, surface)
415            reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_SPECIFIC_SURFACE_ERR,
416                                                    surface_error)
417
418        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_INVARIANT, qstar_total)
419        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_INVARIANT_ERR, qstar_total_error)
420        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_LOW_QSTAR, qstar_low)
421        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_LOW_QSTAR_ERR, qstar_low_err)
422        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_HIGH_QSTAR, qstar_high)
423        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_HIGH_QSTAR_ERR, qstar_high_err)
424        return self.model
425
426
427    def updateModelFromThread(self, widget, value):
428        """
429        Update the model in the main thread
430        """
431        item = QtGui.QStandardItem(str(float('%.3g' % value)))
432        self.model.setItem(widget, item)
433
434    def title(self):
435        """ Perspective name """
436        return "Invariant panel"
437
438    def onStatus(self):
439        """
440        Display Invariant Details panel when clicking on Status button
441        """
442        self.detailsDialog.setModel(self.model)
443        self.detailsDialog.showDialog()
444        self.cmdStatus.setEnabled(False)
445
446    def onHelp(self):
447        """ Display help when clicking on Help button """
448        treeLocation = "/user/qtgui/Perspectives/Invariant/invariant_help.html"
449        self.parent.showHelp(treeLocation)
450
451    def setupSlots(self):
452        """ """
453        self.cmdCalculate.clicked.connect(self.calculateInvariant)
454        self.cmdStatus.clicked.connect(self.onStatus)
455        self.cmdHelp.clicked.connect(self.onHelp)
456
457        self.chkLowQ.stateChanged.connect(self.stateChanged)
458        self.chkLowQ.stateChanged.connect(self.checkQExtrapolatedData)
459
460        self.chkHighQ.stateChanged.connect(self.stateChanged)
461        self.chkHighQ.stateChanged.connect(self.checkQExtrapolatedData)
462
463        # slots for the Guinier and PowerLaw radio buttons at low Q
464        # since they are not auto-exclusive
465        self.rbGuinier.toggled.connect(self.lowGuinierAndPowerToggle)
466
467        self.rbPowerLawLowQ.toggled.connect(self.lowGuinierAndPowerToggle)
468
469        self.rbFitHighQ.toggled.connect(self.hiFitAndFixToggle)
470
471        self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
472
473        self.model.itemChanged.connect(self.modelChanged)
474
475        # update model from gui editing by users
476        self.txtBackgd.textChanged.connect(self.updateFromGui)
477
478        self.txtScale.textChanged.connect(self.updateFromGui)
479
480        self.txtContrast.textChanged.connect(self.updateFromGui)
481
482        self.txtPorodCst.textChanged.connect(self.updateFromGui)
483
484        self.txtPowerLowQ.textChanged.connect(self.updateFromGui)
485
486        self.txtPowerHighQ.textChanged.connect(self.updateFromGui)
487
488        self.txtNptsLowQ.textChanged.connect(self.updateFromGui)
489
490        self.txtNptsHighQ.textChanged.connect(self.updateFromGui)
491
492        # check values of n_points compared to distribution length
493        if self.txtNptsLowQ.isEnabled():
494            self.txtNptsLowQ.textChanged.connect(self.checkLength)
495
496        if self.txtNptsHighQ.isEnabled():
497            self.txtNptsHighQ.textChanged.connect(self.checkLength)
498
499    def stateChanged(self):
500        """
501        Catch modifications from low- and high-Q extrapolation check boxes
502        """
503        sender = self.sender()
504
505        itemf = QtGui.QStandardItem(str(sender.isChecked()).lower())
506        if sender.text() == 'Enable Low-Q extrapolation':
507            self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
508
509        if sender.text() == 'Enable High-Q extrapolation':
510            self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
511
512    def checkLength(self):
513        """
514        Validators of number of points for extrapolation.
515        Error if it is larger than the distribution length
516        """
517        try:
518            int_value = int(self.sender().text())
519        except ValueError:
520            self.sender().setStyleSheet(BG_RED)
521            self.cmdCalculate.setEnabled(False)
522            return
523
524        if self._data:
525            if len(self._data.x) < int_value:
526                self.sender().setStyleSheet(BG_RED)
527                logging.warning('The number of points must be smaller than {}'.format(len(self._data.x)))
528                self.cmdCalculate.setEnabled(False)
529            else:
530                self.sender().setStyleSheet(BG_WHITE)
531                self.cmdCalculate.setEnabled(True)
532        else:
533            # logging.info('no data is loaded')
534            self.cmdCalculate.setEnabled(False)
535
536    def modelChanged(self, item):
537        """ Update when model changed """
538        if item.row() == WIDGETS.W_ENABLE_LOWQ:
539            toggle = (str(item.text()) == 'true')
540            self._low_extrapolate = toggle
541            self.lowQToggle(toggle)
542        elif item.row() == WIDGETS.W_ENABLE_HIGHQ:
543            toggle = (str(item.text()) == 'true')
544            self._high_extrapolate = toggle
545            self.highQToggle(toggle)
546
547    def checkQExtrapolatedData(self):
548        """
549        Match status of low or high-Q extrapolated data checkbox in
550        DataExplorer with low or high-Q extrapolation checkbox in invariant
551        panel
552        """
553        # name to search in DataExplorer
554        if 'Low' in str(self.sender().text()):
555            name = "Low-Q extrapolation"
556        if 'High' in str(self.sender().text()):
557            name = "High-Q extrapolation"
558
559        GuiUtils.updateModelItemStatus(self._manager.filesWidget.model,
560                                       self._path, name,
561                                       self.sender().checkState())
562
563    def updateFromGui(self):
564        """ Update model when new user inputs """
565        possible_senders = ['txtBackgd', 'txtContrast', 'txtPorodCst',
566                            'txtScale', 'txtPowerLowQ', 'txtPowerHighQ',
567                            'txtNptsLowQ', 'txtNptsHighQ']
568
569        related_widgets = [WIDGETS.W_BACKGROUND, WIDGETS.W_CONTRAST,
570                           WIDGETS.W_POROD_CST, WIDGETS.W_SCALE,
571                           WIDGETS.W_LOWQ_POWER_VALUE, WIDGETS.W_HIGHQ_POWER_VALUE,
572                           WIDGETS.W_NPTS_LOWQ, WIDGETS.W_NPTS_HIGHQ]
573
574        related_internal_values = [self._background, self._contrast,
575                                   self._porod, self._scale,
576                                   self._low_power_value,
577                                   self._high_power_value,
578                                   self._low_points, self._high_points]
579
580        item = QtGui.QStandardItem(self.sender().text())
581
582        index_elt = possible_senders.index(self.sender().objectName())
583
584        self.model.setItem(related_widgets[index_elt], item)
585        try:
586            related_internal_values[index_elt] = float(self.sender().text())
587            self.sender().setStyleSheet(BG_WHITE)
588            self.cmdCalculate.setEnabled(True)
589        except ValueError:
590            # empty field, just skip
591            self.sender().setStyleSheet(BG_RED)
592            self.cmdCalculate.setEnabled(False)
593
594    def lowGuinierAndPowerToggle(self, toggle):
595        """
596        Guinier and Power radio buttons cannot be selected at the same time
597        If Power is selected, Fit and Fix radio buttons are visible and
598        Power line edit can be edited if Fix is selected
599        """
600        if self.sender().text() == 'Guinier':
601            self._low_guinier = toggle
602
603            toggle = not toggle
604            self.rbPowerLawLowQ.setChecked(toggle)
605
606            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
607            self.rbFitLowQ.setVisible(toggle)
608            self.rbFixLowQ.setVisible(toggle)
609
610            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
611
612        else:
613            self._low_guinier = not toggle
614
615            self.rbGuinier.setChecked(not toggle)
616
617            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
618            self.rbFitLowQ.setVisible(toggle)
619            self.rbFixLowQ.setVisible(toggle)
620
621            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
622
623    def lowFitAndFixToggle(self, toggle):
624        """ Fit and Fix radiobuttons cannot be selected at the same time """
625        self._low_fit = toggle
626
627        toggle = not toggle
628        self.txtPowerLowQ.setEnabled(toggle)
629
630    def hiFitAndFixToggle(self, toggle):
631        """
632        Enable editing of power exponent if Fix for high Q is checked
633        Disable otherwise
634        """
635        self.txtPowerHighQ.setEnabled(not toggle)
636
637    def highQToggle(self, clicked):
638        """ Disable/enable High Q extrapolation """
639        self.rbFitHighQ.setEnabled(clicked)
640        self.rbFixHighQ.setEnabled(clicked)
641        self.txtNptsHighQ.setEnabled(clicked)
642        self.txtPowerHighQ.setEnabled(clicked)
643
644    def lowQToggle(self, clicked):
645        """ Disable / enable Low Q extrapolation """
646        self.rbGuinier.setEnabled(clicked)
647        self.rbPowerLawLowQ.setEnabled(clicked)
648        self.txtNptsLowQ.setEnabled(clicked)
649        # Enable subelements
650        self.rbFitLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
651        self.rbFixLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
652        self.rbFitLowQ.setEnabled(clicked)  # and not self._low_guinier)
653        self.rbFixLowQ.setEnabled(clicked)  # and not self._low_guinier)
654
655        self.txtPowerLowQ.setEnabled(clicked
656                                    and not self._low_guinier
657                                    and not self._low_fit)
658
659    def setupModel(self):
660        """ """
661        # filename
662        item = QtGui.QStandardItem(self._path)
663        self.model.setItem(WIDGETS.W_FILENAME, item)
664
665        # add Q parameters to the model
666        qmin = 0.0
667        item = QtGui.QStandardItem(str(qmin))
668        self.model.setItem(WIDGETS.W_QMIN, item)
669        qmax = 0.0
670        item = QtGui.QStandardItem(str(qmax))
671        self.model.setItem(WIDGETS.W_QMAX, item)
672
673        # add custom input params
674        item = QtGui.QStandardItem(str(self._background))
675        self.model.setItem(WIDGETS.W_BACKGROUND, item)
676        item = QtGui.QStandardItem(str(self._contrast))
677        self.model.setItem(WIDGETS.W_CONTRAST, item)
678        item = QtGui.QStandardItem(str(self._scale))
679        self.model.setItem(WIDGETS.W_SCALE, item)
680        # leave line edit empty if Porod constant not defined
681        if self._porod != None:
682            item = QtGui.QStandardItem(str(self._porod))
683        else:
684            item = QtGui.QStandardItem(str(''))
685        self.model.setItem(WIDGETS.W_POROD_CST, item)
686
687        # Dialog elements
688        itemf = QtGui.QStandardItem("false")
689        self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
690        itemf = QtGui.QStandardItem("false")
691        self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
692
693        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
694        self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
695        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
696        self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
697
698        itemt = QtGui.QStandardItem("true")
699        self.model.setItem(WIDGETS.W_LOWQ_GUINIER, itemt)
700
701        itemt = QtGui.QStandardItem("true")
702        self.model.setItem(WIDGETS.W_LOWQ_FIT, itemt)
703        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
704        self.model.setItem(WIDGETS.W_LOWQ_POWER_VALUE, item)
705
706        itemt = QtGui.QStandardItem("true")
707        self.model.setItem(WIDGETS.W_HIGHQ_FIT, itemt)
708        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
709        self.model.setItem(WIDGETS.W_HIGHQ_POWER_VALUE, item)
710
711    def setupMapper(self):
712        # Set up the mapper.
713        self.mapper = QtWidgets.QDataWidgetMapper(self)
714        self.mapper.setOrientation(QtCore.Qt.Vertical)
715        self.mapper.setModel(self.model)
716
717        # Filename
718        self.mapper.addMapping(self.txtName, WIDGETS.W_FILENAME)
719
720        # Qmin/Qmax
721        self.mapper.addMapping(self.txtTotalQMin, WIDGETS.W_QMIN)
722        self.mapper.addMapping(self.txtTotalQMax, WIDGETS.W_QMAX)
723
724        # Background
725        self.mapper.addMapping(self.txtBackgd, WIDGETS.W_BACKGROUND)
726
727        # Scale
728        self.mapper.addMapping(self.txtScale, WIDGETS.W_SCALE)
729
730        # Contrast
731        self.mapper.addMapping(self.txtContrast, WIDGETS.W_CONTRAST)
732
733        # Porod constant
734        self.mapper.addMapping(self.txtPorodCst, WIDGETS.W_POROD_CST)
735
736        # Lowq/Highq items
737        self.mapper.addMapping(self.chkLowQ, WIDGETS.W_ENABLE_LOWQ)
738        self.mapper.addMapping(self.chkHighQ, WIDGETS.W_ENABLE_HIGHQ)
739
740        self.mapper.addMapping(self.txtNptsLowQ, WIDGETS.W_NPTS_LOWQ)
741        self.mapper.addMapping(self.rbGuinier, WIDGETS.W_LOWQ_GUINIER)
742        self.mapper.addMapping(self.rbFitLowQ, WIDGETS.W_LOWQ_FIT)
743        self.mapper.addMapping(self.txtPowerLowQ, WIDGETS.W_LOWQ_POWER_VALUE)
744
745        self.mapper.addMapping(self.txtNptsHighQ, WIDGETS.W_NPTS_HIGHQ)
746        self.mapper.addMapping(self.rbFitHighQ, WIDGETS.W_HIGHQ_FIT)
747        self.mapper.addMapping(self.txtPowerHighQ, WIDGETS.W_HIGHQ_POWER_VALUE)
748   
749        # Output
750        self.mapper.addMapping(self.txtVolFract, WIDGETS.W_VOLUME_FRACTION)
751        self.mapper.addMapping(self.txtVolFractErr, WIDGETS.W_VOLUME_FRACTION_ERR)
752        self.mapper.addMapping(self.txtSpecSurf, WIDGETS.W_SPECIFIC_SURFACE)
753        self.mapper.addMapping(self.txtSpecSurfErr, WIDGETS.W_SPECIFIC_SURFACE_ERR)
754        self.mapper.addMapping(self.txtInvariantTot, WIDGETS.W_INVARIANT)
755        self.mapper.addMapping(self.txtInvariantTotErr, WIDGETS.W_INVARIANT_ERR)
756
757        self.mapper.toFirst()
758
759    def setData(self, data_item=None, is_batch=False):
760        """
761        Obtain a QStandardItem object and dissect it to get Data1D/2D
762        Pass it over to the calculator
763        """
764        assert data_item is not None
765
766        if self.txtName.text() == data_item[0].text():
767            logging.info('This file is already loaded in Invariant panel.')
768            return
769
770        if not isinstance(data_item, list):
771            msg = "Incorrect type passed to the Invariant Perspective"
772            raise AttributeError(msg)
773
774        if not isinstance(data_item[0], QtGui.QStandardItem):
775            msg = "Incorrect type passed to the Invariant Perspective"
776            raise AttributeError(msg)
777
778        # only 1 file can be loaded
779        self._model_item = data_item[0]
780
781        # Extract data on 1st child - this is the Data1D/2D component
782        data = GuiUtils.dataFromItem(self._model_item)
783        self.model.item(WIDGETS.W_FILENAME).setData(self._model_item.text())
784        # update GUI and model with info from loaded data
785        self.updateGuiFromFile(data=data)
786
787    def updateGuiFromFile(self, data=None):
788        """
789        update display in GUI and plot
790        """
791        self._data = data
792
793        # plot loaded file
794        if not isinstance(self._data, Data1D):
795            msg = "Invariant cannot be computed with 2D data."
796            raise ValueError(msg)
797
798        try:
799            filename = data.filename
800        except:
801            msg = 'No filename chosen.'
802            raise ValueError(msg)
803        try:
804            qmin = min(self._data.x)
805            qmax = max(self._data.x)
806        except:
807            msg = "Unable to find q min/max of \n data named %s" % \
808                  data.filename
809            raise ValueError(msg)
810
811        # update model with input form files: filename, qmin, qmax
812        self.model.item(WIDGETS.W_FILENAME).setText(filename)
813        self.model.item(WIDGETS.W_QMIN).setText(str(qmin))
814        self.model.item(WIDGETS.W_QMAX).setText(str(qmax))
815        self._path = filename
816
817        # Calculate and add to GUI: volume fraction, invariant total,
818        # and specific surface if porod checked
819        self.calculateInvariant()
820
821    def allowBatch(self):
822        """
823        Tell the caller that we don't accept multiple data instances
824        """
825        return False
Note: See TracBrowser for help on using the repository browser.