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

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

Add commandline checking to CorfuncPerspective?

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