source: sasview/src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py @ 42d79fc

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 42d79fc was 42d79fc, checked in by wojciech, 2 years ago

Disabling reporting for perspectives other than fitting

  • Property mode set to 100644
File size: 15.1 KB
Line 
1"""
2This module provides the intelligence behind the gui interface for Corfunc.
3"""
4# pylint: disable=E1101
5
6# global
7from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg \
8    as FigureCanvas
9from matplotlib.figure import Figure
10from numpy.linalg.linalg import LinAlgError
11
12from PyQt5 import QtCore
13from PyQt5 import QtGui, QtWidgets
14
15# sas-global
16# pylint: disable=import-error, no-name-in-module
17import sas.qtgui.Utilities.GuiUtils as GuiUtils
18from sas.sascalc.corfunc.corfunc_calculator import CorfuncCalculator
19# pylint: enable=import-error, no-name-in-module
20
21# local
22from .UI.CorfuncPanel import Ui_CorfuncDialog
23from .CorfuncUtils import WIDGETS as W
24
25
26class MyMplCanvas(FigureCanvas):
27    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
28    def __init__(self, model, width=5, height=4, dpi=100):
29        self.model = model
30        self.fig = Figure(figsize=(width, height), dpi=dpi)
31        self.axes = self.fig.add_subplot(111)
32
33        FigureCanvas.__init__(self, self.fig)
34
35        self.data = None
36        self.extrap = None
37
38    def draw_q_space(self):
39        """Draw the Q space data in the plot window
40
41        This draws the q space data in self.data, as well
42        as the bounds set by self.qmin, self.qmax1, and self.qmax2.
43        It will also plot the extrpolation in self.extrap, if it exists."""
44
45        # TODO: add interactivity to axvlines so qlimits are immediately updated!
46        self.fig.clf()
47
48        self.axes = self.fig.add_subplot(111)
49        self.axes.set_xscale("log")
50        self.axes.set_yscale("log")
51
52        qmin = float(self.model.item(W.W_QMIN).text())
53        qmax1 = float(self.model.item(W.W_QMAX).text())
54        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
55
56        if self.data:
57            self.axes.plot(self.data.x, self.data.y)
58            self.axes.axvline(qmin)
59            self.axes.axvline(qmax1)
60            self.axes.axvline(qmax2)
61            self.axes.set_xlim(min(self.data.x) / 2,
62                               max(self.data.x) * 1.5 - 0.5 * min(self.data.x))
63        if self.extrap:
64            self.axes.plot(self.extrap.x, self.extrap.y)
65
66        self.draw()
67
68    def draw_real_space(self):
69        """
70        This function draws the real space data onto the plot
71
72        The 1d correlation function in self.data, the 3d correlation function
73        in self.data3, and the interface distribution function in self.data_idf
74        are all draw in on the plot in linear cooredinates."""
75        self.fig.clf()
76
77        self.axes = self.fig.add_subplot(111)
78        self.axes.set_xscale("linear")
79        self.axes.set_yscale("linear")
80
81        if self.data:
82            data1, data3, data_idf = self.data
83            self.axes.plot(data1.x, data1.y, label="1D Correlation")
84            self.axes.plot(data3.x, data3.y, label="3D Correlation")
85            self.axes.plot(data_idf.x, data_idf.y,
86                           label="Interface Distribution Function")
87            self.axes.set_xlim(min(data1.x), max(data1.x) / 4)
88            self.axes.legend()
89
90        self.draw()
91
92
93class CorfuncWindow(QtWidgets.QDialog, Ui_CorfuncDialog):
94    """Displays the correlation function analysis of sas data."""
95    name = "Corfunc"  # For displaying in the combo box
96
97    trigger = QtCore.pyqtSignal(tuple)
98
99# pylint: disable=unused-argument
100    def __init__(self, parent=None):
101        super(CorfuncWindow, self).__init__()
102        self.setupUi(self)
103
104        self.setWindowTitle("Corfunc Perspective")
105
106        self.parent = parent
107        self.mapper = None
108        self.model = QtGui.QStandardItemModel(self)
109        self.communicate = GuiUtils.Communicate()
110        self._calculator = CorfuncCalculator()
111        self._allow_close = False
112        self._model_item = None
113        self.txtLowerQMin.setText("0.0")
114        self.txtLowerQMin.setEnabled(False)
115
116        self._canvas = MyMplCanvas(self.model)
117        self.mainVerticalLayout.insertWidget(0, self._canvas)
118
119        # Connect buttons to slots.
120        # Needs to be done early so default values propagate properly.
121        self.setup_slots()
122
123        # Set up the model.
124        self.setup_model()
125
126        #Disable reporting results
127        self.parent._workspace.actionReport.setEnabled(False)
128
129        # Set up the mapper
130        self.setup_mapper()
131
132    def setup_slots(self):
133        """Connect the buttons to their appropriate slots."""
134        self.cmdExtrapolate.clicked.connect(self.extrapolate)
135        self.cmdExtrapolate.setEnabled(False)
136        self.cmdTransform.clicked.connect(self.transform)
137        self.cmdTransform.setEnabled(False)
138
139        self.cmdCalculateBg.clicked.connect(self.calculate_background)
140        self.cmdCalculateBg.setEnabled(False)
141        self.cmdHelp.clicked.connect(self.showHelp)
142
143        self.model.itemChanged.connect(self.model_changed)
144
145        self.trigger.connect(self.finish_transform)
146
147    def setup_model(self):
148        """Populate the model with default data."""
149        self.model.setItem(W.W_QMIN,
150                           QtGui.QStandardItem("0.01"))
151        self.model.setItem(W.W_QMAX,
152                           QtGui.QStandardItem("0.20"))
153        self.model.setItem(W.W_QCUTOFF,
154                           QtGui.QStandardItem("0.22"))
155        self.model.setItem(W.W_BACKGROUND,
156                           QtGui.QStandardItem("0"))
157        #self.model.setItem(W.W_TRANSFORM,
158        #                   QtGui.QStandardItem("Fourier"))
159        self.model.setItem(W.W_GUINIERA,
160                           QtGui.QStandardItem("0.0"))
161        self.model.setItem(W.W_GUINIERB,
162                           QtGui.QStandardItem("0.0"))
163        self.model.setItem(W.W_PORODK,
164                           QtGui.QStandardItem("0.0"))
165        self.model.setItem(W.W_PORODSIGMA,
166                           QtGui.QStandardItem("0.0"))
167        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem(str(0)))
168        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem(str(0)))
169        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem(str(0)))
170        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem(str(0)))
171        self.model.setItem(W.W_POLY, QtGui.QStandardItem(str(0)))
172        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem(str(0)))
173
174    def model_changed(self, _):
175        """Actions to perform when the data is updated"""
176        if not self.mapper:
177            return
178        self.mapper.toFirst()
179        self._canvas.draw_q_space()
180
181    def _update_calculator(self):
182        self._calculator.lowerq = float(self.model.item(W.W_QMIN).text())
183        qmax1 = float(self.model.item(W.W_QMAX).text())
184        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
185        self._calculator.upperq = (qmax1, qmax2)
186        self._calculator.background = \
187            float(self.model.item(W.W_BACKGROUND).text())
188
189    def extrapolate(self):
190        """Extend the experiemntal data with guinier and porod curves."""
191        self._update_calculator()
192        try:
193            params, extrapolation, _ = self._calculator.compute_extrapolation()
194            self.model.setItem(W.W_GUINIERA, QtGui.QStandardItem("{:.3g}".format(params['A'])))
195            self.model.setItem(W.W_GUINIERB, QtGui.QStandardItem("{:.3g}".format(params['B'])))
196            self.model.setItem(W.W_PORODK, QtGui.QStandardItem("{:.3g}".format(params['K'])))
197            self.model.setItem(W.W_PORODSIGMA,
198                               QtGui.QStandardItem("{:.4g}".format(params['sigma'])))
199
200            self._canvas.extrap = extrapolation
201            self._canvas.draw_q_space()
202            self.cmdTransform.setEnabled(True)
203        except (LinAlgError, ValueError):
204            message = "These is not enough data in the fitting range. "\
205                      "Try decreasing the upper Q, increasing the "\
206                      "cutoff Q, or increasing the lower Q."
207            QtWidgets.QMessageBox.warning(self, "Calculation Error",
208                                      message)
209            self._canvas.extrap = None
210            self._canvas.draw_q_space()
211
212
213    def transform(self):
214        """Calculate the real space version of the extrapolation."""
215        #method = self.model.item(W.W_TRANSFORM).text().lower()
216
217        method = "fourier"
218
219        extrap = self._canvas.extrap
220        background = float(self.model.item(W.W_BACKGROUND).text())
221
222        def updatefn(msg):
223            """Report progress of transformation."""
224            self.communicate.statusBarUpdateSignal.emit(msg)
225
226        def completefn(transforms):
227            """Extract the values from the transforms and plot"""
228            self.trigger.emit(transforms)
229
230        self._update_calculator()
231        self._calculator.compute_transform(extrap, method, background,
232                                           completefn, updatefn)
233
234
235    def finish_transform(self, transforms):
236        params = self._calculator.extract_parameters(transforms[0])
237        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem("{:.3g}".format(params['d0'])))
238        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem("{:.3g}".format(params['dtr'])))
239        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem("{:.3g}".format(params['Lc'])))
240        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem("{:.3g}".format(params['fill'])))
241        self.model.setItem(W.W_POLY, QtGui.QStandardItem("{:.3g}".format(params['A'])))
242        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem("{:.3g}".format(params['max'])))
243        #self._realplot.data = transforms
244
245        self.update_real_space_plot(transforms)
246
247        #self._realplot.draw_real_space()
248
249    def update_real_space_plot(self, datas):
250        """take the datas tuple and create a plot in DE"""
251
252        assert isinstance(datas, tuple)
253        plot_id = id(self)
254        titles = ['1D Correlation', '3D Correlation', 'Interface Distribution Function']
255        for i, plot in enumerate(datas):
256            plot_to_add = self.parent.createGuiData(plot)
257            # set plot properties
258            title = plot_to_add.title
259            plot_to_add.scale = 'linear'
260            plot_to_add.symbol = 'Line'
261            plot_to_add._xaxis = "x"
262            plot_to_add._xunit = "A"
263            plot_to_add._yaxis = "\Gamma"
264            if i < len(titles):
265                title = titles[i]
266                plot_to_add.name = titles[i]
267            GuiUtils.updateModelItemWithPlot(self._model_item, plot_to_add, title)
268            #self.axes.set_xlim(min(data1.x), max(data1.x) / 4)
269        pass
270
271    def setup_mapper(self):
272        """Creating mapping between model and gui elements."""
273        self.mapper = QtWidgets.QDataWidgetMapper(self)
274        self.mapper.setOrientation(QtCore.Qt.Vertical)
275        self.mapper.setModel(self.model)
276
277        self.mapper.addMapping(self.txtLowerQMax, W.W_QMIN)
278        self.mapper.addMapping(self.txtUpperQMin, W.W_QMAX)
279        self.mapper.addMapping(self.txtUpperQMax, W.W_QCUTOFF)
280        self.mapper.addMapping(self.txtBackground, W.W_BACKGROUND)
281        #self.mapper.addMapping(self.transformCombo, W.W_TRANSFORM)
282
283        self.mapper.addMapping(self.txtGuinierA, W.W_GUINIERA)
284        self.mapper.addMapping(self.txtGuinierB, W.W_GUINIERB)
285        self.mapper.addMapping(self.txtPorodK, W.W_PORODK)
286        self.mapper.addMapping(self.txtPorodSigma, W.W_PORODSIGMA)
287
288        self.mapper.addMapping(self.txtAvgCoreThick, W.W_CORETHICK)
289        self.mapper.addMapping(self.txtAvgIntThick, W.W_INTTHICK)
290        self.mapper.addMapping(self.txtAvgHardBlock, W.W_HARDBLOCK)
291        self.mapper.addMapping(self.txtPolydisp, W.W_POLY)
292        self.mapper.addMapping(self.txtLongPeriod, W.W_PERIOD)
293        self.mapper.addMapping(self.txtLocalCrystal, W.W_CRYSTAL)
294
295        self.mapper.toFirst()
296
297    def calculate_background(self):
298        """Find a good estimate of the background value."""
299        self._update_calculator()
300        try:
301            background = self._calculator.compute_background()
302            temp = QtGui.QStandardItem("{:.4g}".format(background))
303            self.model.setItem(W.W_BACKGROUND, temp)
304        except (LinAlgError, ValueError):
305            message = "These is not enough data in the fitting range. "\
306                      "Try decreasing the upper Q or increasing the cutoff Q"
307            QtWidgets.QMessageBox.warning(self, "Calculation Error",
308                                      message)
309
310
311    # pylint: disable=invalid-name
312    def showHelp(self):
313        """
314        Opens a webpage with help on the perspective
315        """
316        """ Display help when clicking on Help button """
317        treeLocation = "/user/qtgui/Perspectives/Corfunc/corfunc_help.html"
318        self.parent.showHelp(treeLocation)
319
320    @staticmethod
321    def allowBatch():
322        """
323        We cannot perform corfunc analysis in batch at this time.
324        """
325        return False
326
327    def setData(self, data_item, is_batch=False):
328        """
329        Obtain a QStandardItem object and dissect it to get Data1D/2D
330        Pass it over to the calculator
331        """
332        if not isinstance(data_item, list):
333            msg = "Incorrect type passed to the Corfunc Perpsective"
334            raise AttributeError(msg)
335
336        if not isinstance(data_item[0], QtGui.QStandardItem):
337            msg = "Incorrect type passed to the Corfunc Perspective"
338            raise AttributeError(msg)
339
340        model_item = data_item[0]
341        data = GuiUtils.dataFromItem(model_item)
342        self._model_item = model_item
343        self._calculator.set_data(data)
344        self.cmdCalculateBg.setEnabled(True)
345        self.cmdExtrapolate.setEnabled(True)
346
347        self.model.setItem(W.W_GUINIERA, QtGui.QStandardItem(""))
348        self.model.setItem(W.W_GUINIERB, QtGui.QStandardItem(""))
349        self.model.setItem(W.W_PORODK, QtGui.QStandardItem(""))
350        self.model.setItem(W.W_PORODSIGMA, QtGui.QStandardItem(""))
351        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem(""))
352        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem(""))
353        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem(""))
354        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem(""))
355        self.model.setItem(W.W_POLY, QtGui.QStandardItem(""))
356        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem(""))
357
358        self._canvas.data = data
359        self._canvas.extrap = None
360        self._canvas.draw_q_space()
361        self.cmdTransform.setEnabled(False)
362
363        #self._realplot.data = None
364        #self._realplot.draw_real_space()
365
366    def setClosable(self, value=True):
367        """
368        Allow outsiders close this widget
369        """
370        assert isinstance(value, bool)
371
372        self._allow_close = value
373
374    def closeEvent(self, event):
375        """
376        Overwrite QDialog close method to allow for custom widget close
377        """
378        if self._allow_close:
379            # reset the closability flag
380            self.setClosable(value=False)
381            # Tell the MdiArea to close the container
382            if self.parent:
383                self.parentWidget().close()
384            event.accept()
385        else:
386            event.ignore()
387            # Maybe we should just minimize
388            self.setWindowState(QtCore.Qt.WindowMinimized)
389
390    def title(self):
391        """
392        Window title function used by certain error messages.
393        Check DataExplorer.py, line 355
394        """
395        return "Corfunc Perspective"
396    # pylint: enable=invalid-name
Note: See TracBrowser for help on using the repository browser.