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

Last change on this file since ecdf172 was ecdf172, checked in by Adam Washington <adam.washington@…>, 6 years ago

Fix error message bug when sent multiple data sets

  • Property mode set to 100644
File size: 12.4 KB
Line 
1"""
2This module provides the intelligence behind the gui interface for Corfunc.
3"""
4# pylint: disable=E1101
5
6# global
7from PyQt4 import QtCore
8from PyQt4 import QtGui
9
10# sas-global
11# pylint: disable=import-error, no-name-in-module
12import sas.qtgui.Utilities.GuiUtils as GuiUtils
13from sas.sascalc.corfunc.corfunc_calculator import CorfuncCalculator
14# pylint: enable=import-error, no-name-in-module
15
16# local
17from UI.CorfuncPanel import Ui_CorfuncDialog
18from CorfuncUtils import WIDGETS as W
19
20from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
21    as FigureCanvas
22from matplotlib.figure import Figure
23
24
25class MyMplCanvas(FigureCanvas):
26    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
27    def __init__(self, model, width=5, height=4, dpi=100):
28        self.model = model
29        self.fig = Figure(figsize=(width, height), dpi=dpi)
30        self.axes = self.fig.add_subplot(111)
31
32        FigureCanvas.__init__(self, self.fig)
33
34        self.data = None
35        self.extrap = None
36
37    def draw_q_space(self):
38        """Draw the Q space data in the plot window
39
40        This draws the q space data in self.data, as well
41        as the bounds set by self.qmin, self.qmax1, and self.qmax2.
42        It will also plot the extrpolation in self.extrap, if it exists."""
43
44        self.fig.clf()
45
46        self.axes = self.fig.add_subplot(111)
47        self.axes.set_xscale("log")
48        self.axes.set_yscale("log")
49
50        qmin = float(self.model.item(W.W_QMIN).text())
51        qmax1 = float(self.model.item(W.W_QMAX).text())
52        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
53
54        if self.data:
55            self.axes.plot(self.data.x, self.data.y)
56            self.axes.axvline(qmin)
57            self.axes.axvline(qmax1)
58            self.axes.axvline(qmax2)
59            self.axes.set_xlim(min(self.data.x) / 2,
60                               max(self.data.x) * 1.5 - 0.5 * min(self.data.x))
61        if self.extrap:
62            self.axes.plot(self.extrap.x, self.extrap.y)
63
64        self.draw()
65
66    def draw_real_space(self):
67        """
68        This function draws the real space data onto the plot
69
70        The 1d correlation function in self.data, the 3d correlation function
71        in self.data3, and the interface distribution function in self.data_idf
72        are all draw in on the plot in linear cooredinates."""
73        self.fig.clf()
74
75        self.axes = self.fig.add_subplot(111)
76        self.axes.set_xscale("linear")
77        self.axes.set_yscale("linear")
78
79        if self.data:
80            data1, data3, data_idf = self.data
81            self.axes.plot(data1.x, data1.y, label="1D Correlation")
82            self.axes.plot(data3.x, data3.y, label="3D Correlation")
83            self.axes.plot(data_idf.x, data_idf.y,
84                           label="Interface Distribution Function")
85            self.axes.set_xlim(min(data1.x), max(data1.x) / 4)
86            self.axes.legend()
87
88        self.draw()
89
90
91class CorfuncWindow(QtGui.QDialog, Ui_CorfuncDialog):
92    """Displays the correlation function analysis of sas data."""
93    name = "Corfunc"  # For displaying in the combo box
94
95    trigger = QtCore.pyqtSignal(tuple)
96
97# pylint: disable=unused-argument
98    def __init__(self, parent=None):
99        super(CorfuncWindow, self).__init__()
100        self.setupUi(self)
101
102        self.setWindowTitle("Corfunc Perspective")
103
104        self.mapper = None
105        self.model = QtGui.QStandardItemModel(self)
106        self.communicate = GuiUtils.Communicate()
107        self._calculator = CorfuncCalculator()
108        self._allow_close = True
109
110        self._canvas = MyMplCanvas(self.model)
111        self._realplot = MyMplCanvas(self.model)
112        self.verticalLayout_7.insertWidget(0, self._canvas)
113        self.verticalLayout_7.insertWidget(1, self._realplot)
114
115        # Connect buttons to slots.
116        # Needs to be done early so default values propagate properly.
117        self.setup_slots()
118
119        # Set up the model.
120        self.setup_model()
121
122        # Set up the mapper
123        self.setup_mapper()
124
125    def setup_slots(self):
126        """Connect the buttons to their appropriate slots."""
127        self.extrapolateBtn.clicked.connect(self.extrapolate)
128        self.extrapolateBtn.setEnabled(False)
129        self.transformBtn.clicked.connect(self.transform)
130        self.transformBtn.setEnabled(False)
131
132        self.calculateBgBtn.clicked.connect(self.calculate_background)
133        self.calculateBgBtn.setEnabled(False)
134
135        self.model.itemChanged.connect(self.model_changed)
136
137        self.trigger.connect(self.finish_transform)
138
139    def setup_model(self):
140        """Populate the model with default data."""
141        self.model.setItem(W.W_QMIN,
142                           QtGui.QStandardItem("0.01"))
143        self.model.setItem(W.W_QMAX,
144                           QtGui.QStandardItem("0.20"))
145        self.model.setItem(W.W_QCUTOFF,
146                           QtGui.QStandardItem("0.22"))
147        self.model.setItem(W.W_BACKGROUND,
148                           QtGui.QStandardItem("0"))
149        self.model.setItem(W.W_TRANSFORM,
150                           QtGui.QStandardItem("Fourier"))
151        self.model.setItem(W.W_GUINIERA,
152                           QtGui.QStandardItem("0.0"))
153        self.model.setItem(W.W_GUINIERB,
154                           QtGui.QStandardItem("0.0"))
155        self.model.setItem(W.W_PORODK,
156                           QtGui.QStandardItem("0.0"))
157        self.model.setItem(W.W_PORODSIGMA,
158                           QtGui.QStandardItem("0.0"))
159        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem(str(0)))
160        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem(str(0)))
161        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem(str(0)))
162        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem(str(0)))
163        self.model.setItem(W.W_POLY, QtGui.QStandardItem(str(0)))
164        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem(str(0)))
165
166    def model_changed(self, _):
167        """Actions to perform when the data is updated"""
168        if not self.mapper:
169            return
170        self.mapper.toFirst()
171        self._canvas.draw_q_space()
172
173    def _update_calculator(self):
174        self._calculator.lowerq = float(self.model.item(W.W_QMIN).text())
175        qmax1 = float(self.model.item(W.W_QMAX).text())
176        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
177        self._calculator.upperq = (qmax1, qmax2)
178        self._calculator.background = \
179            float(self.model.item(W.W_BACKGROUND).text())
180
181    def extrapolate(self):
182        """Extend the experiemntal data with guinier and porod curves."""
183        self._update_calculator()
184        params, extrapolation, _ = self._calculator.compute_extrapolation()
185
186        self.model.setItem(W.W_GUINIERA, QtGui.QStandardItem(str(params['A'])))
187        self.model.setItem(W.W_GUINIERB, QtGui.QStandardItem(str(params['B'])))
188        self.model.setItem(W.W_PORODK, QtGui.QStandardItem(str(params['K'])))
189        self.model.setItem(W.W_PORODSIGMA,
190                           QtGui.QStandardItem(str(params['sigma'])))
191
192        self._canvas.extrap = extrapolation
193        self._canvas.draw_q_space()
194        self.transformBtn.setEnabled(True)
195
196    def transform(self):
197        """Calculate the real space version of the extrapolation."""
198        method = str(self.model.item(W.W_TRANSFORM).text()).lower()
199
200        extrap = self._canvas.extrap
201        background = float(self.model.item(W.W_BACKGROUND).text())
202
203        def updatefn(msg):
204            """Report progress of transformation."""
205            self.communicate.statusBarUpdateSignal.emit(msg)
206
207        def completefn(transforms):
208            """Extract the values from the transforms and plot"""
209            self.trigger.emit(transforms)
210
211        self._update_calculator()
212        self._calculator.compute_transform(extrap, method, background,
213                                           completefn, updatefn)
214
215
216    def finish_transform(self, transforms):
217        params = self._calculator.extract_parameters(transforms[0])
218        self.model.setItem(W.W_CORETHICK,
219                           QtGui.QStandardItem(str(params['d0'])))
220        self.model.setItem(W.W_INTTHICK,
221                           QtGui.QStandardItem(str(params['dtr'])))
222        self.model.setItem(W.W_HARDBLOCK,
223                           QtGui.QStandardItem(str(params['Lc'])))
224        self.model.setItem(W.W_CRYSTAL,
225                           QtGui.QStandardItem(str(params['fill'])))
226        self.model.setItem(W.W_POLY,
227                           QtGui.QStandardItem(str(params['A'])))
228        self.model.setItem(W.W_PERIOD,
229                           QtGui.QStandardItem(str(params['max'])))
230        self._realplot.data = transforms
231        self._realplot.draw_real_space()
232
233    def setup_mapper(self):
234        """Creating mapping between model and gui elements."""
235        self.mapper = QtGui.QDataWidgetMapper(self)
236        self.mapper.setOrientation(QtCore.Qt.Vertical)
237        self.mapper.setModel(self.model)
238
239        self.mapper.addMapping(self.qMin, W.W_QMIN)
240        self.mapper.addMapping(self.qMax1, W.W_QMAX)
241        self.mapper.addMapping(self.qMax2, W.W_QCUTOFF)
242        self.mapper.addMapping(self.bg, W.W_BACKGROUND)
243        self.mapper.addMapping(self.transformCombo, W.W_TRANSFORM)
244
245        self.mapper.addMapping(self.guinierA, W.W_GUINIERA)
246        self.mapper.addMapping(self.guinierB, W.W_GUINIERB)
247        self.mapper.addMapping(self.porodK, W.W_PORODK)
248        self.mapper.addMapping(self.porodSigma, W.W_PORODSIGMA)
249
250        self.mapper.addMapping(self.avgCoreThick, W.W_CORETHICK)
251        self.mapper.addMapping(self.avgIntThick, W.W_INTTHICK)
252        self.mapper.addMapping(self.avgHardBlock, W.W_HARDBLOCK)
253        self.mapper.addMapping(self.polydisp, W.W_POLY)
254        self.mapper.addMapping(self.longPeriod, W.W_PERIOD)
255        self.mapper.addMapping(self.localCrystal, W.W_CRYSTAL)
256
257        self.mapper.toFirst()
258
259    def calculate_background(self):
260        """Find a good estimate of the background value."""
261        self._update_calculator()
262        background = self._calculator.compute_background()
263        temp = QtGui.QStandardItem(str(background))
264        self.model.setItem(W.W_BACKGROUND, temp)
265
266    # pylint: disable=invalid-name
267    @staticmethod
268    def allowBatch():
269        """
270        We cannot perform corfunc analysis in batch at this time.
271        """
272        return False
273
274    def setData(self, data_item, is_batch=False):
275        """
276        Obtain a QStandardItem object and dissect it to get Data1D/2D
277        Pass it over to the calculator
278        """
279        if not isinstance(data_item, list):
280            msg = "Incorrect type passed to the Corfunc Perpsective"
281            raise AttributeError(msg)
282
283        if not isinstance(data_item[0], QtGui.QStandardItem):
284            msg = "Incorrect type passed to the Corfunc Perspective"
285            raise AttributeError(msg)
286
287        model_item = data_item[0]
288        data = GuiUtils.dataFromItem(model_item)
289        self._calculator.set_data(data)
290        self.calculateBgBtn.setEnabled(True)
291        self.extrapolateBtn.setEnabled(True)
292
293        self.model.setItem(W.W_GUINIERA, QtGui.QStandardItem(""))
294        self.model.setItem(W.W_GUINIERB, QtGui.QStandardItem(""))
295        self.model.setItem(W.W_PORODK, QtGui.QStandardItem(""))
296        self.model.setItem(W.W_PORODSIGMA, QtGui.QStandardItem(""))
297        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem(""))
298        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem(""))
299        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem(""))
300        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem(""))
301        self.model.setItem(W.W_POLY, QtGui.QStandardItem(""))
302        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem(""))
303
304        self._canvas.data = data
305        self._canvas.extrap = None
306        self._canvas.draw_q_space()
307        self.transformBtn.setEnabled(False)
308
309        self._realplot.data = None
310        self._realplot.draw_real_space()
311
312    def setClosable(self, value=True):
313        """
314        Allow outsiders close this widget
315        """
316        assert isinstance(value, bool)
317
318        self._allow_close = value
319
320    def title(self):
321        """
322        Window title function used by certain error messages.
323        Check DataExplorer.py, line 355
324        """
325        return "Corfunc Perspective"
326    # pylint: enable=invalid-name
327
328
329if __name__ == "__main__":
330    APPLICATION = QtGui.QApplication([])
331    import qt4reactor
332    qt4reactor.install()
333    # DO NOT move the following import to the top!
334    # (unless you know what you're doing)
335    from twisted.internet import reactor
336    DIALOG = CorfuncWindow(reactor)
337    DIALOG.show()
338    reactor.run()
Note: See TracBrowser for help on using the repository browser.