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

ESS_GUI_opencl
Last change on this file since e0d5b63 was 73fb503, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Allow for smearing selection on theory plots.

  • Property mode set to 100644
File size: 15.8 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
[e900a47]66        self.smear_type = None
67        self.dq_l = None
68        self.dq_r = None
69
70        # current pinhole/slot values
71        self.pinhole = 0.0
72        self.slit_height = 0.0
73        self.slit_width = 0.0
74
[98b13f72]75        # Let only floats in the line edits
[d6b8a1d]76        self.txtSmearDown.setValidator(GuiUtils.DoubleValidator())
77        self.txtSmearUp.setValidator(GuiUtils.DoubleValidator())
[98b13f72]78
79        # Attach slots
80        self.cbSmearing.currentIndexChanged.connect(self.onIndexChange)
81        self.cbSmearing.setCurrentIndex(0)
[9a7c81c]82        self.txtSmearUp.setText(str(DEFAULT_PINHOLE_UP))
83        self.txtSmearDown.setText(str(DEFAULT_PINHOLE_DOWN))
[98b13f72]84
[e1e3e09]85        self.initModel()
86        self.initMapper()
87
88    def initModel(self):
89        """
90        Initialize the state
91        """
92        self.model = QtGui.QStandardItemModel()
[b3e8629]93        for model_item in range(len(MODEL)):
[e1e3e09]94            self.model.setItem(model_item, QtGui.QStandardItem())
[98b13f72]95        # Attach slot
[e1e3e09]96        self.model.dataChanged.connect(self.onModelChange)
97
98    def initMapper(self):
99        """
100        Initialize model item <-> UI element mapping
101        """
102        self.mapper = DataWidgetMapper(self)
103
104        self.mapper.setModel(self.model)
105        self.mapper.setOrientation(QtCore.Qt.Vertical)
106
107        self.mapper.addMapping(self.txtSmearUp,   MODEL.index('PINHOLE_MIN'))
108        self.mapper.addMapping(self.txtSmearDown, MODEL.index('PINHOLE_MAX'))
109        self.mapper.addMapping(self.cbAccuracy,   MODEL.index('ACCURACY'))
[4992ff2]110
[9a7c81c]111        self.mapper.toFirst()
[e1e3e09]112
[9a7c81c]113    def updateData(self, data=None):
[e1e3e09]114        """
[9a7c81c]115        Update control elements based on data and model passed
[e1e3e09]116        """
[73fb503]117        # retain the index
118        cur_index = self.cbSmearing.currentIndex()
[e1e3e09]119        self.cbSmearing.clear()
120        self.cbSmearing.addItem("None")
[9a7c81c]121        self.gAccuracy.setVisible(False)
122        self.data = data
[e1e3e09]123        if data is None:
124            self.setElementsVisibility(False)
[b415abc]125        model = self.kernel_model
126        self.updateKernelModel(model)
[73fb503]127        # already has the required setup so modify the index
128        self.cbSmearing.setCurrentIndex(cur_index)
[e1e3e09]129
[9a7c81c]130    def updateKernelModel(self, kernel_model=None):
131        """
132        Update the model
133        """
134        self.kernel_model = kernel_model
[f20ea3f]135        self.cbSmearing.blockSignals(True)
[b14db78]136        self.cbSmearing.clear()
137        self.cbSmearing.addItem("None")
[8b6e4be]138        if self.data is None:
139            self.setElementsVisibility(False)
140            return
[b14db78]141        # Find out if data has dQ
142        (self.smear_type, self.dq_l, self.dq_r) = self.getSmearInfo()
[f20ea3f]143        index_to_show = 0
[b14db78]144        if self.smear_type is not None:
145            self.cbSmearing.addItem(SMEARING_QD)
[f20ea3f]146            index_to_show = 1
[b415abc]147
148        if self.kernel_model is None:
149            # No model definend yet - just use data file smearing, if any
150            self.cbSmearing.blockSignals(False)
151            self.cbSmearing.setCurrentIndex(index_to_show)
152            return
153
[b14db78]154        if isinstance(self.data, Data1D):
[8b6e4be]155            self.cbSmearing.addItems(SMEARING_1D)
156        else:
157            self.cbSmearing.addItems(SMEARING_2D)
[f20ea3f]158        self.cbSmearing.blockSignals(False)
159        self.cbSmearing.setCurrentIndex(index_to_show)
[9a7c81c]160
161    def smearer(self):
162        """ Returns the current smearer """
163        return self.current_smearer
164
[e1e3e09]165    def onIndexChange(self, index):
166        """
167        Callback for smearing combobox index change
168        """
[b14db78]169        text = self.cbSmearing.currentText()
170        if text == 'None':
[e1e3e09]171            self.setElementsVisibility(False)
[9a7c81c]172            self.current_smearer = None
[b14db78]173        elif text == "Use dQ Data":
174            self.setElementsVisibility(True)
175            self.setDQLabels()
176            if self.smear_type == "Pinhole":
177                self.onPinholeSmear()
178            else:
179                self.onSlitSmear()
180        elif text == "Custom Pinhole Smear":
[e1e3e09]181            self.setElementsVisibility(True)
182            self.setPinholeLabels()
[9a7c81c]183            self.onPinholeSmear()
[b14db78]184        elif text == "Custom Slit Smear":
[e1e3e09]185            self.setElementsVisibility(True)
186            self.setSlitLabels()
[9a7c81c]187            self.onSlitSmear()
188        self.smearingChangedSignal.emit()
[e1e3e09]189
[9a7c81c]190    def onModelChange(self):
[e1e3e09]191        """
[9a7c81c]192        Respond to model change by notifying any listeners
[e1e3e09]193        """
[9a7c81c]194        # Recalculate the smearing
195        index = self.cbSmearing.currentIndex()
[e900a47]196        ## update the backup values based on model choice
197        smearing, accuracy, d_down, d_up = self.state()
198        # don't save the state if dQ Data
199        if smearing == "Custom Pinhole Smear":
200            self.pinhole = d_down
201            self.accuracy = accuracy
202        elif smearing == 'Custom Slit Smear':
203            self.slit_height = d_up
204            self.slit_width = d_down
205
[9a7c81c]206        self.onIndexChange(index)
[e1e3e09]207
208    def setElementsVisibility(self, visible):
209        """
210        Labels and linedits visibility control
211        """
212        self.lblSmearDown.setVisible(visible)
213        self.lblSmearUp.setVisible(visible)
214        self.txtSmearDown.setVisible(visible)
215        self.txtSmearUp.setVisible(visible)
[9a7c81c]216        self.lblUnitUp.setVisible(visible)
217        self.lblUnitDown.setVisible(visible)
[e1e3e09]218        self.setAccuracyVisibility()
219
220    def setAccuracyVisibility(self):
221        """
222        Accuracy combobox visibility
223        """
[9a7c81c]224        if isinstance(self.data, Data2D) and self.cbSmearing.currentIndex() == 1:
225            self.gAccuracy.setVisible(True)
[e1e3e09]226        else:
[9a7c81c]227            self.gAccuracy.setVisible(False)
[e1e3e09]228
229    def setPinholeLabels(self):
230        """
231        Use pinhole labels
232        """
[9a7c81c]233        self.txtSmearDown.setVisible(False)
234        self.lblSmearDown.setText('')
235        self.lblUnitDown.setText('')
236        if isinstance(self.data, Data2D):
237            self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
238            self.lblSmearUp.setText('<html><head/><body><p>&lt;dQ<span style=" vertical-align:sub;">low</span>&gt;</p></body></html>')
239        else:
[b415abc]240            self.lblSmearUp.setText('<html><head/><body><p>dQ/Q</p></body></html>')
[9a7c81c]241            self.lblUnitUp.setText('%')
[e900a47]242        self.txtSmearUp.setText(str(self.pinhole))
243
[b14db78]244        self.txtSmearDown.setEnabled(True)
245        self.txtSmearUp.setEnabled(True)
[e1e3e09]246
247    def setSlitLabels(self):
248        """
249        Use pinhole labels
250        """
251        self.lblSmearUp.setText('Slit height')
252        self.lblSmearDown.setText('Slit width')
[9a7c81c]253        self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
254        self.lblUnitDown.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
[e900a47]255        self.txtSmearDown.setText(str(self.slit_height))
256        self.txtSmearUp.setText(str(self.slit_width))
[b14db78]257        self.txtSmearDown.setEnabled(True)
258        self.txtSmearUp.setEnabled(True)
259
260    def setDQLabels(self):
261        """
[b415abc]262        Use appropriate labels
[b14db78]263        """
264        if self.smear_type == "Pinhole":
[b415abc]265            text_down = '<html><head/><body><p>[dQ/Q]<span style=" vertical-align:sub;">max</span></p></body></html>'
266            text_up = '<html><head/><body><p>[dQ/Q]<span style=" vertical-align:sub;">min</span></p></body></html>'
267            text_unit = '%'
268        elif self.smear_type == "Slit":
269            text_down = '<html><head/><body><p>Slit width</p></body></html>'
270            text_up = '<html><head/><body><p>Slit height</p></body></html>'
271            text_unit = '<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>'
[b14db78]272        else:
[b415abc]273            text_unit = '%'
274            text_up = '<html><head/><body><p>&lsaquo;dQ/Q&rsaquo;<span style=" vertical-align:sub;">r</span></p></body></html>'
275            text_down = '<html><head/><body><p>&lsaquo;dQ/Q&rsaquo;<span style=" vertical-align:sub;">&phi;</span></p></body></html>'
276
277        self.lblSmearDown.setText(text_down)
278        self.lblSmearUp.setText(text_up)
279
280        self.lblUnitUp.setText(text_unit)
281        self.lblUnitDown.setText(text_unit)
282
[b14db78]283        self.txtSmearDown.setText(str(self.dq_r))
284        self.txtSmearUp.setText(str(self.dq_l))
285        self.txtSmearDown.setEnabled(False)
286        self.txtSmearUp.setEnabled(False)
[e1e3e09]287
288    def state(self):
289        """
290        Returns current state of controls
291        """
[9a7c81c]292        smearing = self.cbSmearing.currentText()
[180bd54]293        accuracy = ""
294        d_down = None
295        d_up = None
296        if smearing != "None":
297            accuracy = str(self.model.item(MODEL.index('ACCURACY')).text())
298            try:
[e900a47]299                d_down = float(self.txtSmearUp.text())
[180bd54]300            except ValueError:
301                d_down = None
302            try:
[e900a47]303                d_up = float(self.txtSmearDown.text())
[180bd54]304            except ValueError:
305                d_up = None
[98b13f72]306
307        return (smearing, accuracy, d_down, d_up)
[672b8ab]308
309    def setState(self, smearing, accuracy, d_down, d_up):
310        """
311        Sets new values for the controls
312        """
313        # Update the model -> controls update automatically
314        if accuracy is not None:
315            self.model.item(MODEL.index('ACCURACY')).setText(accuracy)
316        if d_down is not None:
317            self.model.item(MODEL.index('PINHOLE_MIN')).setText(d_down)
318        if d_up is not None:
319            self.model.item(MODEL.index('PINHOLE_MAX')).setText(d_up)
320
[9a7c81c]321    def onPinholeSmear(self):
322        """
323        Create a custom pinhole smear object that will change the way residuals
324        are compute when fitting
325        """
326        _, accuracy, d_percent, _ = self.state()
327        if d_percent is None or d_percent == 0.0:
328            self.current_smearer=None
329            return
330        percent = d_percent/100.0
331        # copy data
332        data = copy.deepcopy(self.data)
333        if isinstance(self.data, Data2D):
334            len_data = len(data.data)
335            data.dqx_data = np.zeros(len_data)
336            data.dqy_data = np.zeros(len_data)
337            data.dqx_data[data.dqx_data == 0] = percent * data.qx_data
338            data.dqy_data[data.dqy_data == 0] = percent * data.qy_data
[b2572f4]339            q = np.sqrt(data.qx_data**2 + data.qy_data**2)
340            data.dx_data = data.dqy_data = percent*q
[9a7c81c]341        else:
342            len_data = len(data.x)
343            data.dx = np.zeros(len_data)
344            data.dx = percent * data.x
345            data.dxl = None
346            data.dxw = None
347
348        self.current_smearer = smear_selection(data, self.kernel_model)
349        # need to set accuracy for 2D
350        if isinstance(self.data, Data2D):
351            backend_accuracy = ACCURACY_DICT.get(accuracy)
352            if backend_accuracy:
353                self.current_smearer.set_accuracy(accuracy=backend_accuracy)
354
355    def onSlitSmear(self):
356        """
357        Create a custom slit smear object that will change the way residuals
358        are compute when fitting
359        """
360        _, accuracy, d_height, d_width = self.state()
[04e1c80]361
[9a7c81c]362        # Check changes in slit width
363        if d_width is None:
364            d_width = 0.0
365        if d_height is None:
366            d_height = 0.0
367
[e900a47]368        self.slit_width = d_width
369        self.slit_height = d_height
370
[9a7c81c]371        if isinstance(self.data, Data2D):
[04e1c80]372            self.current_smearer = smear_selection(self.data, self.kernel_model)
[9a7c81c]373            return
374        # make sure once more if it is smearer
375        data = copy.deepcopy(self.data)
376        data_len = len(data.x)
377        data.dx = None
378        data.dxl = None
379        data.dxw = None
380
381        try:
382            self.dxl = d_height
383            data.dxl = self.dxl * np.ones(data_len)
384        except:
385            self.dxl = None
386            data.dxl = np.zeros(data_len)
387        try:
388            self.dxw = d_width
389            data.dxw = self.dxw * np.ones(data_len)
390        except:
391            self.dxw = None
392            data.dxw = np.zeros(data_len)
393
394        self.current_smearer = smear_selection(data, self.kernel_model)
[b14db78]395
396    def getSmearInfo(self):
397        """
398        Get the smear info from data.
399
400        :return: self.smear_type, self.dq_l and self.dq_r,
[b415abc]401            respectively the type of the smear, the average <dq/q> radial(p)
402            and <dq/q> theta (s)s for 2D pinhole resolution in % (slit is not
403            currently supported in 2D), (dq/q)_min and (dq/q)_max for 1D pinhole
404            smeared data, again in %, and dxl and/or dxw for slit smeared data
405            given in 1/A and assumed constant.
[b14db78]406        """
407        # default
408        smear_type = None
409        dq_l = None
410        dq_r = None
411        data = self.data
412        if self.data is None:
413            return smear_type, dq_l, dq_r
[b415abc]414        # First check if data is 2D
415        # If so check that data set has smearing info and that none are zero.
416        # Otherwise no smearing can be applied using smear from data (a Gaussian
417        # width of zero will cause a divide by zero error)
[b14db78]418        elif isinstance(data, Data2D):
419            if data.dqx_data is None or data.dqy_data is None:
420                return smear_type, dq_l, dq_r
[b415abc]421            elif data.dqx_data.any() != 0 and data.dqy_data.any() != 0:
[b14db78]422                smear_type = "Pinhole2d"
[a5f7bf4]423                dq_l = GuiUtils.formatNumber(np.average(data.dqx_data/np.abs(data.qx_data))*100., high=True)
424                dq_r = GuiUtils.formatNumber(np.average(data.dqy_data/np.abs(data.qy_data))*100., high=True)
[b14db78]425                return smear_type, dq_l, dq_r
426            else:
427                return smear_type, dq_l, dq_r
428        # check if it is pinhole smear and get min max if it is.
[b415abc]429        if data.dx is not None and np.all(data.dx):
[b14db78]430            smear_type = "Pinhole"
[b415abc]431            dq_l = GuiUtils.formatNumber(data.dx[0]/data.x[0] *100., high=True)
432            dq_r = GuiUtils.formatNumber(data.dx[-1]/data.x[-1] *100., high=True)
[b14db78]433
434        # check if it is slit smear and get min max if it is.
435        elif data.dxl is not None or data.dxw is not None:
436            smear_type = "Slit"
437            if data.dxl is not None and np.all(data.dxl, 0):
[b415abc]438                dq_l = GuiUtils.formatNumber(data.dxl[0])
[b14db78]439            if data.dxw is not None and np.all(data.dxw, 0):
[b415abc]440                dq_r = GuiUtils.formatNumber(data.dxw[0])
[b14db78]441
442        return smear_type, dq_l, dq_r
Note: See TracBrowser for help on using the repository browser.