source: sasview/src/sas/qtgui/Perspectives/Fitting/SmearingWidget.py @ 04e1c80

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 04e1c80 was 04e1c80, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Minor usability fixes for instrumental resolution.

  • Property mode set to 100644
File size: 14.0 KB
RevLine 
[e1e3e09]1"""
2Widget/logic for smearing data.
3"""
[9a7c81c]4import copy
5import numpy as np
[4992ff2]6from PyQt5 import QtCore
7from PyQt5 import QtGui
8from PyQt5 import QtWidgets
[e1e3e09]9
[9a7c81c]10from sas.sascalc.fit.qsmearing import smear_selection
[dc5ef15]11from sas.qtgui.Plotting.PlotterData import Data1D
12from sas.qtgui.Plotting.PlotterData import Data2D
[d6b8a1d]13import sas.qtgui.Utilities.GuiUtils as GuiUtils
[e1e3e09]14
15# Local UI
[1bc27f1]16from sas.qtgui.Perspectives.Fitting.UI.SmearingWidgetUI import Ui_SmearingWidgetUI
[e1e3e09]17
[4992ff2]18class DataWidgetMapper(QtWidgets.QDataWidgetMapper):
[180bd54]19    """
20    Custom version of the standard QDataWidgetMapper allowing for proper
21    response to index change in comboboxes
22    """
23    def addMapping(self, widget, section, propertyName=None):
24        if propertyName is None:
25            super(DataWidgetMapper, self).addMapping(widget, section)
26        else:
27            super(DataWidgetMapper, self).addMapping(widget, section, propertyName)
28
[4992ff2]29        if isinstance(widget, QtWidgets.QComboBox):
[180bd54]30            delegate = self.itemDelegate()
[e1e3e09]31            widget.currentIndexChanged.connect(lambda: delegate.commitData.emit(widget))
32
33SMEARING_1D = ["Custom Pinhole Smear", "Custom Slit Smear"]
34SMEARING_2D = ["Custom Pinhole Smear"]
[b14db78]35SMEARING_QD = "Use dQ Data"
[e1e3e09]36
37MODEL = [
38    'SMEARING',
39    'PINHOLE_MIN',
40    'PINHOLE_MAX',
41    'ACCURACY']
[9a7c81c]42ACCURACY_DICT={'Low': 'low',
43               'Medium': 'med',
44               'High': 'high',
45               'Extra high': 'xhigh'}
46
47DEFAULT_PINHOLE_UP=0.0
48DEFAULT_PINHOLE_DOWN=0.0
[e1e3e09]49
[4992ff2]50class SmearingWidget(QtWidgets.QWidget, Ui_SmearingWidgetUI):
[9a7c81c]51    smearingChangedSignal = QtCore.pyqtSignal()
[e1e3e09]52    def __init__(self, parent=None):
53        super(SmearingWidget, self).__init__()
54
55        self.setupUi(self)
[98b13f72]56
57        # Local model for holding data
[e1e3e09]58        self.model = None
[98b13f72]59        # Mapper for model update
[e1e3e09]60        self.mapper = None
[9a7c81c]61        # Data from the widget
62        self.data = None
63        self.current_smearer = None
[8b6e4be]64        self.kernel_model = None
[b14db78]65        # dQ data variables
66        smear_type = None
67        dq_l = None
68        dq_r = None
[98b13f72]69        # Let only floats in the line edits
[d6b8a1d]70        self.txtSmearDown.setValidator(GuiUtils.DoubleValidator())
71        self.txtSmearUp.setValidator(GuiUtils.DoubleValidator())
[98b13f72]72
73        # Attach slots
74        self.cbSmearing.currentIndexChanged.connect(self.onIndexChange)
75        self.cbSmearing.setCurrentIndex(0)
[9a7c81c]76        self.txtSmearUp.setText(str(DEFAULT_PINHOLE_UP))
77        self.txtSmearDown.setText(str(DEFAULT_PINHOLE_DOWN))
[98b13f72]78
[e1e3e09]79        self.initModel()
80        self.initMapper()
81
82    def initModel(self):
83        """
84        Initialize the state
85        """
86        self.model = QtGui.QStandardItemModel()
[b3e8629]87        for model_item in range(len(MODEL)):
[e1e3e09]88            self.model.setItem(model_item, QtGui.QStandardItem())
[98b13f72]89        # Attach slot
[e1e3e09]90        self.model.dataChanged.connect(self.onModelChange)
91
92    def initMapper(self):
93        """
94        Initialize model item <-> UI element mapping
95        """
96        self.mapper = DataWidgetMapper(self)
97
98        self.mapper.setModel(self.model)
99        self.mapper.setOrientation(QtCore.Qt.Vertical)
100
101        self.mapper.addMapping(self.txtSmearUp,   MODEL.index('PINHOLE_MIN'))
102        self.mapper.addMapping(self.txtSmearDown, MODEL.index('PINHOLE_MAX'))
103        self.mapper.addMapping(self.cbAccuracy,   MODEL.index('ACCURACY'))
[4992ff2]104
[9a7c81c]105        self.mapper.toFirst()
[e1e3e09]106
[9a7c81c]107    def updateData(self, data=None):
[e1e3e09]108        """
[9a7c81c]109        Update control elements based on data and model passed
[e1e3e09]110        """
111        self.cbSmearing.clear()
112        self.cbSmearing.addItem("None")
[9a7c81c]113        self.gAccuracy.setVisible(False)
114        self.data = data
[e1e3e09]115        if data is None:
116            self.setElementsVisibility(False)
[f20ea3f]117        if self.kernel_model is not None:
118            # model already present - recalculate
119            model = self.kernel_model
120            self.updateKernelModel(model)
[e1e3e09]121
[9a7c81c]122    def updateKernelModel(self, kernel_model=None):
123        """
124        Update the model
125        """
126        self.kernel_model = kernel_model
[f20ea3f]127        self.cbSmearing.blockSignals(True)
[b14db78]128        self.cbSmearing.clear()
129        self.cbSmearing.addItem("None")
[8b6e4be]130        if self.data is None:
131            self.setElementsVisibility(False)
132            return
133        if self.kernel_model is None:
134            return
[b14db78]135        # Find out if data has dQ
136        (self.smear_type, self.dq_l, self.dq_r) = self.getSmearInfo()
[f20ea3f]137        index_to_show = 0
[b14db78]138        if self.smear_type is not None:
139            self.cbSmearing.addItem(SMEARING_QD)
[f20ea3f]140            index_to_show = 1
[b14db78]141        if isinstance(self.data, Data1D):
[8b6e4be]142            self.cbSmearing.addItems(SMEARING_1D)
143        else:
144            self.cbSmearing.addItems(SMEARING_2D)
[f20ea3f]145        self.cbSmearing.blockSignals(False)
146        self.cbSmearing.setCurrentIndex(index_to_show)
[9a7c81c]147
148    def smearer(self):
149        """ Returns the current smearer """
150        return self.current_smearer
151
[e1e3e09]152    def onIndexChange(self, index):
153        """
154        Callback for smearing combobox index change
155        """
[b14db78]156        text = self.cbSmearing.currentText()
157        if text == 'None':
[e1e3e09]158            self.setElementsVisibility(False)
[9a7c81c]159            self.current_smearer = None
[b14db78]160        elif text == "Use dQ Data":
161            self.setElementsVisibility(True)
162            self.setDQLabels()
163            if self.smear_type == "Pinhole":
164                self.onPinholeSmear()
165            else:
166                self.onSlitSmear()
167        elif text == "Custom Pinhole Smear":
[e1e3e09]168            self.setElementsVisibility(True)
169            self.setPinholeLabels()
[9a7c81c]170            self.onPinholeSmear()
[b14db78]171        elif text == "Custom Slit Smear":
[e1e3e09]172            self.setElementsVisibility(True)
173            self.setSlitLabels()
[9a7c81c]174            self.onSlitSmear()
175        self.smearingChangedSignal.emit()
[e1e3e09]176
[9a7c81c]177    def onModelChange(self):
[e1e3e09]178        """
[9a7c81c]179        Respond to model change by notifying any listeners
[e1e3e09]180        """
[9a7c81c]181        # Recalculate the smearing
182        index = self.cbSmearing.currentIndex()
183        self.onIndexChange(index)
[e1e3e09]184
185    def setElementsVisibility(self, visible):
186        """
187        Labels and linedits visibility control
188        """
189        self.lblSmearDown.setVisible(visible)
190        self.lblSmearUp.setVisible(visible)
191        self.txtSmearDown.setVisible(visible)
192        self.txtSmearUp.setVisible(visible)
[9a7c81c]193        self.lblUnitUp.setVisible(visible)
194        self.lblUnitDown.setVisible(visible)
[e1e3e09]195        self.setAccuracyVisibility()
196
197    def setAccuracyVisibility(self):
198        """
199        Accuracy combobox visibility
200        """
[9a7c81c]201        if isinstance(self.data, Data2D) and self.cbSmearing.currentIndex() == 1:
202            self.gAccuracy.setVisible(True)
[e1e3e09]203        else:
[9a7c81c]204            self.gAccuracy.setVisible(False)
[e1e3e09]205
206    def setPinholeLabels(self):
207        """
208        Use pinhole labels
209        """
[9a7c81c]210        self.txtSmearDown.setVisible(False)
211        self.lblSmearDown.setText('')
212        self.lblUnitDown.setText('')
213        if isinstance(self.data, Data2D):
214            self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
215            self.lblSmearUp.setText('<html><head/><body><p>&lt;dQ<span style=" vertical-align:sub;">low</span>&gt;</p></body></html>')
216        else:
217            self.lblSmearUp.setText('<html><head/><body><p>dQ<span style=" vertical-align:sub;">%</span></p></body></html>')
218            self.lblUnitUp.setText('%')
[b14db78]219        self.txtSmearDown.setEnabled(True)
220        self.txtSmearUp.setEnabled(True)
[04e1c80]221        #self.txtSmearDown.setText(str(0.0))
222        #self.txtSmearUp.setText(str(0.0))
[e1e3e09]223
224    def setSlitLabels(self):
225        """
226        Use pinhole labels
227        """
228        self.lblSmearUp.setText('Slit height')
229        self.lblSmearDown.setText('Slit width')
[9a7c81c]230        self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
231        self.lblUnitDown.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
[b14db78]232        self.txtSmearDown.setEnabled(True)
233        self.txtSmearUp.setEnabled(True)
[04e1c80]234        #self.txtSmearDown.setText(str(0.0))
235        #self.txtSmearUp.setText(str(0.0))
[b14db78]236
237    def setDQLabels(self):
238        """
239        Use pinhole labels
240        """
241        if self.smear_type == "Pinhole":
242            self.lblSmearDown.setText('<html><head/><body><p>dQ<span style=" vertical-align:sub;">high</span></p></body></html>')
243            self.lblSmearUp.setText('<html><head/><body><p>dQ<span style=" vertical-align:sub;">low</span></p></body></html>')
244        else:
245            self.lblSmearUp.setText('<dQp>')
246            self.lblSmearDown.setText('<dQs>')
247        self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
248        self.lblUnitDown.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
249        self.txtSmearDown.setText(str(self.dq_r))
250        self.txtSmearUp.setText(str(self.dq_l))
251        self.txtSmearDown.setEnabled(False)
252        self.txtSmearUp.setEnabled(False)
[e1e3e09]253
254    def state(self):
255        """
256        Returns current state of controls
257        """
[9a7c81c]258        smearing = self.cbSmearing.currentText()
[180bd54]259        accuracy = ""
260        d_down = None
261        d_up = None
262        if smearing != "None":
263            accuracy = str(self.model.item(MODEL.index('ACCURACY')).text())
264            try:
265                d_down = float(self.model.item(MODEL.index('PINHOLE_MIN')).text())
266            except ValueError:
267                d_down = None
268            try:
269                d_up = float(self.model.item(MODEL.index('PINHOLE_MAX')).text())
270            except ValueError:
271                d_up = None
[98b13f72]272
273        return (smearing, accuracy, d_down, d_up)
[672b8ab]274
275    def setState(self, smearing, accuracy, d_down, d_up):
276        """
277        Sets new values for the controls
278        """
279        # Update the model -> controls update automatically
[9a7c81c]280        #if smearing is not None:
281            #self.model.item(MODEL.index('SMEARING')).setText(smearing)
[672b8ab]282        if accuracy is not None:
283            self.model.item(MODEL.index('ACCURACY')).setText(accuracy)
284        if d_down is not None:
285            self.model.item(MODEL.index('PINHOLE_MIN')).setText(d_down)
286        if d_up is not None:
287            self.model.item(MODEL.index('PINHOLE_MAX')).setText(d_up)
288
[9a7c81c]289    def onPinholeSmear(self):
290        """
291        Create a custom pinhole smear object that will change the way residuals
292        are compute when fitting
293        """
294        _, accuracy, d_percent, _ = self.state()
295        if d_percent is None or d_percent == 0.0:
296            self.current_smearer=None
297            return
298        percent = d_percent/100.0
299        # copy data
300        data = copy.deepcopy(self.data)
301        if isinstance(self.data, Data2D):
302            len_data = len(data.data)
303            data.dqx_data = np.zeros(len_data)
304            data.dqy_data = np.zeros(len_data)
305            data.dqx_data[data.dqx_data == 0] = percent * data.qx_data
306            data.dqy_data[data.dqy_data == 0] = percent * data.qy_data
307        else:
308            len_data = len(data.x)
309            data.dx = np.zeros(len_data)
310            data.dx = percent * data.x
311            data.dxl = None
312            data.dxw = None
313
314        self.current_smearer = smear_selection(data, self.kernel_model)
315        # need to set accuracy for 2D
316        if isinstance(self.data, Data2D):
317            backend_accuracy = ACCURACY_DICT.get(accuracy)
318            if backend_accuracy:
319                self.current_smearer.set_accuracy(accuracy=backend_accuracy)
320
321    def onSlitSmear(self):
322        """
323        Create a custom slit smear object that will change the way residuals
324        are compute when fitting
325        """
326        _, accuracy, d_height, d_width = self.state()
[04e1c80]327
[9a7c81c]328        # Check changes in slit width
329        if d_width is None:
330            d_width = 0.0
331        if d_height is None:
332            d_height = 0.0
333
334        if isinstance(self.data, Data2D):
[04e1c80]335            self.current_smearer = smear_selection(self.data, self.kernel_model)
[9a7c81c]336            return
337        # make sure once more if it is smearer
338        data = copy.deepcopy(self.data)
339        data_len = len(data.x)
340        data.dx = None
341        data.dxl = None
342        data.dxw = None
343
344        try:
345            self.dxl = d_height
346            data.dxl = self.dxl * np.ones(data_len)
347        except:
348            self.dxl = None
349            data.dxl = np.zeros(data_len)
350        try:
351            self.dxw = d_width
352            data.dxw = self.dxw * np.ones(data_len)
353        except:
354            self.dxw = None
355            data.dxw = np.zeros(data_len)
356
357        self.current_smearer = smear_selection(data, self.kernel_model)
[b14db78]358
359    def getSmearInfo(self):
360        """
361        Get the smear info from data.
362
363        :return: self.smear_type, self.dq_l and self.dq_r,
364            respectively the type of the smear, dq_min and
365            dq_max for pinhole smear data
366            while dxl and dxw for slit smear
367        """
368        # default
369        smear_type = None
370        dq_l = None
371        dq_r = None
372        data = self.data
373        if self.data is None:
374            return smear_type, dq_l, dq_r
375        elif isinstance(data, Data2D):
376            if data.dqx_data is None or data.dqy_data is None:
377                return smear_type, dq_l, dq_r
378            elif data.dqx_data.any() != 0 and data.dqx_data.any() != 0:
379                smear_type = "Pinhole2d"
380                dq_l = GuiUtils.formatNumber(np.average(data.dqx_data))
381                dq_r = GuiUtils.formatNumber(np.average(data.dqy_data))
382                return smear_type, dq_l, dq_r
383            else:
384                return smear_type, dq_l, dq_r
385        # check if it is pinhole smear and get min max if it is.
386        if data.dx is not None and np.any(data.dx):
387            smear_type = "Pinhole"
388            dq_l = data.dx[0]
389            dq_r = data.dx[-1]
390
391        # check if it is slit smear and get min max if it is.
392        elif data.dxl is not None or data.dxw is not None:
393            smear_type = "Slit"
394            if data.dxl is not None and np.all(data.dxl, 0):
395                dq_l = data.dxl[0]
396            if data.dxw is not None and np.all(data.dxw, 0):
397                dq_r = data.dxw[0]
398
399        return smear_type, dq_l, dq_r
Note: See TracBrowser for help on using the repository browser.