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

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_opencl
Last change on this file since b2572f4 was b2572f4, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 10 months ago

Use dQ/|Q| for 2D resolution. Cherry picked from master #209

  • Property mode set to 100644
File size: 14.1 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
[b2572f4]307            q = np.sqrt(data.qx_data**2 + data.qy_data**2)
308            data.dx_data = data.dqy_data = percent*q
[9a7c81c]309        else:
310            len_data = len(data.x)
311            data.dx = np.zeros(len_data)
312            data.dx = percent * data.x
313            data.dxl = None
314            data.dxw = None
315
316        self.current_smearer = smear_selection(data, self.kernel_model)
317        # need to set accuracy for 2D
318        if isinstance(self.data, Data2D):
319            backend_accuracy = ACCURACY_DICT.get(accuracy)
320            if backend_accuracy:
321                self.current_smearer.set_accuracy(accuracy=backend_accuracy)
322
323    def onSlitSmear(self):
324        """
325        Create a custom slit smear object that will change the way residuals
326        are compute when fitting
327        """
328        _, accuracy, d_height, d_width = self.state()
[04e1c80]329
[9a7c81c]330        # Check changes in slit width
331        if d_width is None:
332            d_width = 0.0
333        if d_height is None:
334            d_height = 0.0
335
336        if isinstance(self.data, Data2D):
[04e1c80]337            self.current_smearer = smear_selection(self.data, self.kernel_model)
[9a7c81c]338            return
339        # make sure once more if it is smearer
340        data = copy.deepcopy(self.data)
341        data_len = len(data.x)
342        data.dx = None
343        data.dxl = None
344        data.dxw = None
345
346        try:
347            self.dxl = d_height
348            data.dxl = self.dxl * np.ones(data_len)
349        except:
350            self.dxl = None
351            data.dxl = np.zeros(data_len)
352        try:
353            self.dxw = d_width
354            data.dxw = self.dxw * np.ones(data_len)
355        except:
356            self.dxw = None
357            data.dxw = np.zeros(data_len)
358
359        self.current_smearer = smear_selection(data, self.kernel_model)
[b14db78]360
361    def getSmearInfo(self):
362        """
363        Get the smear info from data.
364
365        :return: self.smear_type, self.dq_l and self.dq_r,
366            respectively the type of the smear, dq_min and
367            dq_max for pinhole smear data
368            while dxl and dxw for slit smear
369        """
370        # default
371        smear_type = None
372        dq_l = None
373        dq_r = None
374        data = self.data
375        if self.data is None:
376            return smear_type, dq_l, dq_r
377        elif isinstance(data, Data2D):
378            if data.dqx_data is None or data.dqy_data is None:
379                return smear_type, dq_l, dq_r
380            elif data.dqx_data.any() != 0 and data.dqx_data.any() != 0:
381                smear_type = "Pinhole2d"
382                dq_l = GuiUtils.formatNumber(np.average(data.dqx_data))
383                dq_r = GuiUtils.formatNumber(np.average(data.dqy_data))
384                return smear_type, dq_l, dq_r
385            else:
386                return smear_type, dq_l, dq_r
387        # check if it is pinhole smear and get min max if it is.
388        if data.dx is not None and np.any(data.dx):
389            smear_type = "Pinhole"
390            dq_l = data.dx[0]
391            dq_r = data.dx[-1]
392
393        # check if it is slit smear and get min max if it is.
394        elif data.dxl is not None or data.dxw is not None:
395            smear_type = "Slit"
396            if data.dxl is not None and np.all(data.dxl, 0):
397                dq_l = data.dxl[0]
398            if data.dxw is not None and np.all(data.dxw, 0):
399                dq_r = data.dxw[0]
400
401        return smear_type, dq_l, dq_r
Note: See TracBrowser for help on using the repository browser.