source: sasview/src/sas/sasgui/perspectives/corfunc/corfunc_panel.py @ f2bbabf

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since f2bbabf was f2bbabf, checked in by lewis, 8 years ago

Fix background input initially appearing red

  • Property mode set to 100644
File size: 16.4 KB
Line 
1import wx
2import sys
3from wx.lib.scrolledpanel import ScrolledPanel
4from sas.sasgui.guiframe.events import PlotQrangeEvent
5from sas.sasgui.guiframe.events import StatusEvent
6from sas.sasgui.guiframe.panel_base import PanelBase
7from sas.sasgui.guiframe.utils import check_float
8from sas.sasgui.guiframe.dataFitting import Data1D
9from sas.sasgui.perspectives.invariant.invariant_widgets import OutputTextCtrl
10from sas.sasgui.perspectives.invariant.invariant_widgets import InvTextCtrl
11from sas.sasgui.perspectives.fitting.basepage import ModelTextCtrl
12from sas.sasgui.perspectives.corfunc.corfunc_state import CorfuncState
13import sas.sasgui.perspectives.corfunc.corfunc
14from sas.sascalc.corfunc.corfunc_calculator import CorfuncCalculator
15
16if sys.platform.count("win32") > 0:
17    _STATICBOX_WIDTH = 350
18    PANEL_WIDTH = 400
19    PANEL_HEIGHT = 700
20    FONT_VARIANT = 0
21else:
22    _STATICBOX_WIDTH = 390
23    PANEL_WIDTH = 430
24    PANEL_HEIGHT = 700
25    FONT_VARIANT = 1
26
27class CorfuncPanel(ScrolledPanel,PanelBase):
28    window_name = "Correlation Function"
29    window_caption = "Correlation Function"
30    CENTER_PANE = True
31
32    def __init__(self, parent, data=None, manager=None, *args, **kwds):
33        kwds["size"] = (PANEL_WIDTH, PANEL_HEIGHT)
34        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
35        ScrolledPanel.__init__(self, parent=parent, *args, **kwds)
36        PanelBase.__init__(self, parent)
37        self.SetupScrolling()
38        self.SetWindowVariant(variant=FONT_VARIANT)
39        self._manager = manager
40        self._data = data # The data to be analysed
41        self._extrapolated_data = None # The extrapolated data set
42        self._data_name_box = None # Text box to show name of file
43        self._background_input = None
44        self._qmin_input = None
45        self._qmax1_input = None
46        self._qmax2_input = None
47        self.qmin = 0
48        self.qmax = (0, 0)
49        self.background = 0
50        # Dictionary for saving IDs of text boxes used to display output data
51        self._output_ids = None
52        self.state = None
53        self._do_layout()
54        self.set_state()
55        self._qmin_input.Bind(wx.EVT_TEXT, self._on_enter_input)
56        self._qmax1_input.Bind(wx.EVT_TEXT, self._on_enter_input)
57        self._qmax2_input.Bind(wx.EVT_TEXT, self._on_enter_input)
58        self._background_input.Bind(wx.EVT_TEXT, self._on_enter_input)
59
60    def set_state(self, state=None, data=None):
61        """
62        Set the state of the panel. If no state is provided, the panel will
63        be set to the default state.
64
65        :param state: A CorfuncState object
66        :param data: A Data1D object
67        """
68        if state is None:
69            self.state = CorfuncState()
70        else:
71            self.state = state
72        if data is not None:
73            self.state.data = data
74        self.set_data(data, set_qrange=False)
75        if self.state.qmin is not None:
76            self.set_qmin(self.state.qmin)
77        if self.state.qmax is not None and self.state.qmax != (None, None):
78            self.set_qmax(tuple(self.state.qmax))
79        if self.state.background is not None:
80            self.set_background(self.state.background)
81
82    def get_state(self):
83        """
84        Return the state of the panel
85        """
86        state = CorfuncState()
87        state.set_saved_state('qmin_tcl', self.qmin)
88        state.set_saved_state('qmax1_tcl', self.qmax[0])
89        state.set_saved_state('qmax2_tcl', self.qmax[1])
90        state.set_saved_state('background_tcl', self.background)
91        if self._data is not None:
92            state.file = self._data.title
93            state.data = self._data
94        self.state = state
95
96        return self.state
97
98    def onSetFocus(self, evt):
99        if evt is not None:
100            evt.Skip()
101        self._validate_inputs()
102
103    def set_data(self, data=None, set_qrange=True):
104        """
105        Update the GUI to reflect new data that has been loaded in
106
107        :param data: The data that has been loaded
108        """
109        if data is None:
110            return
111        self._data_name_box.SetValue(str(data.title))
112        self._data = data
113        if self._manager is not None:
114            from sas.sasgui.perspectives.corfunc.corfunc import IQ_DATA_LABEL
115            self._manager.show_data(data, IQ_DATA_LABEL, reset=True)
116        if set_qrange:
117            lower = data.x[-1]*0.05
118            upper1 = data.x[-1] - lower*5
119            upper2 = data.x[-1]
120            self.set_qmin(lower)
121            self.set_qmax((upper1, upper2))
122            self.set_background(0.0)
123
124    def get_data(self):
125        return self._data
126
127    def compute_extrapolation(self, event=None):
128        if self._data is None:
129            msg = "Data must be loaded in order to perform an extrapolation."
130            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
131            return
132        if not self._validate_inputs:
133            msg = "Invalid Q range entered."
134            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
135            return
136        calculator = CorfuncCalculator(self._data, self.qmin, self.qmax,
137            background=self.background)
138        self._extrapolated_data = calculator.compute_extrapolation()
139        # TODO: Find way to set xlim and ylim so full range of data can be
140        # plotted
141        maxq = self._data.x.max()
142        mask = self._extrapolated_data.x <= maxq
143        numpts = len(self._extrapolated_data.x[mask]) + 250
144        plot_x = self._extrapolated_data.x[0:numpts]
145        plot_y = self._extrapolated_data.y[0:numpts]
146        to_plot = Data1D(plot_x, plot_y)
147        from sas.sasgui.perspectives.corfunc.corfunc import\
148            IQ_EXTRAPOLATED_DATA_LABEL
149        self._manager.show_data(to_plot, IQ_EXTRAPOLATED_DATA_LABEL)
150
151
152    def save_project(self, doc=None):
153        """
154        Return an XML node containing the state of the panel
155
156        :param doc: Am xml node to attach the project state to (optional)
157        """
158        data = self._data
159        state = self.get_state()
160        if data is not None:
161            new_doc, sasentry = self._manager.state_reader._to_xml_doc(data)
162            new_doc = state.toXML(doc=new_doc, entry_node=sasentry)
163            if new_doc is not None:
164                if doc is not None and hasattr(doc, "firstChild"):
165                    child = new_doc.getElementsByTagName("SASentry")
166                    for item in child:
167                        doc.firstChild.appendChild(item)
168                else:
169                    doc = new_doc
170        return doc
171
172    def set_qmin(self, qmin):
173        self.qmin = qmin
174        self._qmin_input.SetValue(str(qmin))
175
176    def set_qmax(self, qmax):
177        self.qmax = qmax
178        self._qmax1_input.SetValue(str(qmax[0]))
179        self._qmax2_input.SetValue(str(qmax[1]))
180
181    def set_background(self, bg):
182        self.background = bg
183        self._background_input.SetValue(str(bg))
184
185
186    def _on_enter_input(self, event=None):
187        """
188        Read values from input boxes and save to memory.
189        """
190        if event is not None: event.Skip()
191        if not self._validate_inputs():
192            return
193        self.qmin = float(self._qmin_input.GetValue())
194        new_qmax1 = float(self._qmax1_input.GetValue())
195        new_qmax2 = float(self._qmax2_input.GetValue())
196        self.qmax = (new_qmax1, new_qmax2)
197        self.background = float(self._background_input.GetValue())
198        from sas.sasgui.perspectives.corfunc.corfunc import GROUP_ID_IQ_DATA,\
199            IQ_DATA_LABEL
200        group_id = GROUP_ID_IQ_DATA
201        if event is not None:
202            wx.PostEvent(self._manager.parent, PlotQrangeEvent(
203                ctrl=[self._qmin_input, self._qmax1_input, self._qmax2_input],
204                active=event.GetEventObject(), id=IQ_DATA_LABEL,
205                group_id=group_id, leftdown=False))
206
207    def _validate_inputs(self):
208        """
209        Check that the values for qmin and qmax in the input boxes are valid
210        """
211        if self._data is None:
212            return False
213        qmin_valid = check_float(self._qmin_input)
214        qmax1_valid = check_float(self._qmax1_input)
215        qmax2_valid = check_float(self._qmax2_input)
216        qmax_valid = qmax1_valid and qmax2_valid
217        background_valid = check_float(self._background_input)
218        msg = ""
219        if (qmin_valid and qmax_valid and background_valid):
220            qmin = float(self._qmin_input.GetValue())
221            qmax1 = float(self._qmax1_input.GetValue())
222            qmax2 = float(self._qmax2_input.GetValue())
223            background = float(self._background_input.GetValue())
224            if not qmin > self._data.x.min():
225                msg = "qmin must be greater than the lowest q value"
226                qmin_valid = False
227            elif qmax2 < qmax1:
228                msg = "qmax1 must be less than qmax2"
229                qmax_valid = False
230            elif qmin > qmax1:
231                msg = "qmin must be less than qmax"
232                qmin_valid = False
233            elif background < 0 or background > self._data.y.max():
234                msg = "background must be positive and less than highest I"
235                background_valid = False
236        if not qmin_valid:
237            self._qmin_input.SetBackgroundColour('pink')
238        if not qmax_valid:
239            self._qmax1_input.SetBackgroundColour('pink')
240            self._qmax2_input.SetBackgroundColour('pink')
241        if not background_valid:
242            self._background_input.SetBackgroundColour('pink')
243            if msg != "":
244                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
245        if (qmin_valid and qmax_valid and background_valid):
246            self._qmin_input.SetBackgroundColour(wx.WHITE)
247            self._qmax1_input.SetBackgroundColour(wx.WHITE)
248            self._qmax2_input.SetBackgroundColour(wx.WHITE)
249            self._background_input.SetBackgroundColour(wx.WHITE)
250        self._qmin_input.Refresh()
251        self._qmax1_input.Refresh()
252        self._qmax2_input.Refresh()
253        self._background_input.Refresh()
254        return (qmin_valid and qmax_valid and background_valid)
255
256    def _do_layout(self):
257        """
258        Draw the window content
259        """
260        vbox = wx.GridBagSizer(0,0)
261
262        # I(q) data box
263        databox = wx.StaticBox(self, -1, "I(Q) Data Source")
264        databox_sizer = wx.StaticBoxSizer(databox, wx.VERTICAL)
265
266        file_sizer = wx.GridBagSizer(5, 5)
267
268        file_name_label = wx.StaticText(self, -1, "Name:")
269        file_sizer.Add(file_name_label, (0, 0), (1, 1),
270            wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
271
272        self._data_name_box = OutputTextCtrl(self, -1,
273            size=(300,20))
274        file_sizer.Add(self._data_name_box, (0, 1), (1, 1),
275            wx.CENTER | wx.ADJUST_MINSIZE, 15)
276
277        file_sizer.AddSpacer((1, 25), pos=(0,2))
278        databox_sizer.Add(file_sizer, wx.TOP, 15)
279
280        vbox.Add(databox_sizer, (0, 0), (1, 1),
281            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE | wx.TOP, 15)
282
283
284        # Parameters
285        qbox = wx.StaticBox(self, -1, "Parameters")
286        qbox_sizer = wx.StaticBoxSizer(qbox, wx.VERTICAL)
287        qbox_sizer.SetMinSize((_STATICBOX_WIDTH, 75))
288
289        q_sizer = wx.GridBagSizer(5, 5)
290
291        # Explanation
292        explanation_txt = ("Corfunc will use all values in the lower range for"
293            " Guinier back extrapolation, and all values in the upper range "
294            "for Porod forward extrapolation.")
295        explanation_label = wx.StaticText(self, -1, explanation_txt,
296            size=(_STATICBOX_WIDTH, 60))
297
298        q_sizer.Add(explanation_label, (0,0), (1,4), wx.LEFT | wx.EXPAND, 5)
299
300        background_label = wx.StaticText(self, -1, "Background:", size=(80,20))
301        q_sizer.Add(background_label, (1,0), (1,1), wx.LEFT | wx.EXPAND, 5)
302
303        self._background_input = ModelTextCtrl(self, -1, size=(50,20),
304            style=wx.TE_PROCESS_ENTER, name='background_input',
305            text_enter_callback=self._on_enter_input)
306        self._background_input.SetToolTipString(("A background value to "
307            "subtract from all intensity values"))
308        q_sizer.Add(self._background_input, (1,1), (1,1),
309            wx.RIGHT | wx.EXPAND, 5)
310
311        qrange_label = wx.StaticText(self, -1, "Q Range:", size=(50,20))
312        q_sizer.Add(qrange_label, (2,0), (1,1), wx.LEFT | wx.EXPAND, 5)
313
314        # Lower Q Range
315        qmin_label = wx.StaticText(self, -1, "Lower:", size=(50,20))
316        qmin_dash_label = wx.StaticText(self, -1, "-", size=(10,20),
317            style=wx.ALIGN_CENTER_HORIZONTAL)
318
319        qmin_lower = OutputTextCtrl(self, -1, size=(50, 20), value="0.0")
320        self._qmin_input = ModelTextCtrl(self, -1, size=(50, 20),
321                        style=wx.TE_PROCESS_ENTER, name='qmin_input',
322                        text_enter_callback=self._on_enter_input)
323        self._qmin_input.SetToolTipString(("Values with q < qmin will be used "
324            "for Guinier back extrapolation"))
325
326        q_sizer.Add(qmin_label, (3, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
327        q_sizer.Add(qmin_lower, (3, 1), (1, 1), wx.LEFT, 5)
328        q_sizer.Add(qmin_dash_label, (3, 2), (1, 1), wx.CENTER | wx.EXPAND, 5)
329        q_sizer.Add(self._qmin_input, (3, 3), (1, 1), wx.LEFT, 5)
330
331        # Upper Q range
332        qmax_tooltip = ("Values with qmax1 < q < qmax2 will be used for Porod"
333            " forward extrapolation")
334
335        qmax_label = wx.StaticText(self, -1, "Upper:", size=(50,20))
336        qmax_dash_label = wx.StaticText(self, -1, "-", size=(10,20),
337            style=wx.ALIGN_CENTER_HORIZONTAL)
338
339        self._qmax1_input = ModelTextCtrl(self, -1, size=(50, 20),
340            style=wx.TE_PROCESS_ENTER, name="qmax1_input",
341            text_enter_callback=self._on_enter_input)
342        self._qmax1_input.SetToolTipString(qmax_tooltip)
343        self._qmax2_input = ModelTextCtrl(self, -1, size=(50, 20),
344            style=wx.TE_PROCESS_ENTER, name="qmax2_input",
345            text_enter_callback=self._on_enter_input)
346        self._qmax2_input.SetToolTipString(qmax_tooltip)
347
348        q_sizer.Add(qmax_label, (4, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
349        q_sizer.Add(self._qmax1_input, (4, 1), (1, 1), wx.LEFT, 5)
350        q_sizer.Add(qmax_dash_label, (4, 2), (1, 1), wx.CENTER | wx.EXPAND, 5)
351        q_sizer.Add(self._qmax2_input, (4,3), (1, 1), wx.LEFT, 5)
352
353        qbox_sizer.Add(q_sizer, wx.TOP, 0)
354
355        vbox.Add(qbox_sizer, (1, 0), (1, 1),
356            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
357
358        # Output data
359        outputbox = wx.StaticBox(self, -1, "Output Measuments")
360        outputbox_sizer = wx.StaticBoxSizer(outputbox, wx.VERTICAL)
361
362        output_sizer = wx.GridBagSizer(5, 5)
363
364        label_strings = [
365            "Long Period (A): ",
366            "Average Hard Block Thickness (A): ",
367            "Average Interface Thickness (A): ",
368            "Average Core Thickness: ",
369            "PolyDispersity: ",
370            "Filling Fraction: "
371        ]
372        self._output_ids = dict()
373        for i in range(len(label_strings)):
374            # Create a label and a text box for each poperty
375            label = wx.StaticText(self, -1, label_strings[i])
376            output_box = OutputTextCtrl(self, wx.NewId(), size=(50, 20),
377                value="-", style=wx.ALIGN_CENTER_HORIZONTAL)
378            # Save the ID of each of the text boxes for accessing after the
379            # output data has been calculated
380            self._output_ids[label_strings[i]] = output_box.GetId()
381            output_sizer.Add(label, (i, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
382            output_sizer.Add(output_box, (i, 2), (1, 1),
383                wx.RIGHT | wx.EXPAND, 15)
384
385        outputbox_sizer.Add(output_sizer, wx.TOP, 0)
386
387        vbox.Add(outputbox_sizer, (2, 0), (1, 1),
388            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
389
390        # Controls
391        controlbox = wx.StaticBox(self, -1, "Controls")
392        controlbox_sizer = wx.StaticBoxSizer(controlbox, wx.VERTICAL)
393
394        controls_sizer = wx.BoxSizer(wx.VERTICAL)
395
396        extrapolate_btn = wx.Button(self, wx.NewId(), "Extrapolate")
397        transform_btn = wx.Button(self, wx.NewId(), "Transform")
398        compute_btn = wx.Button(self, wx.NewId(), "Compute Measuments")
399
400        extrapolate_btn.Bind(wx.EVT_BUTTON, self.compute_extrapolation)
401
402        controls_sizer.Add(extrapolate_btn, wx.CENTER | wx.EXPAND)
403        controls_sizer.Add(transform_btn, wx.CENTER | wx.EXPAND)
404        controls_sizer.Add(compute_btn, wx.CENTER | wx.EXPAND)
405
406        controlbox_sizer.Add(controls_sizer, wx.TOP | wx.EXPAND, 0)
407        vbox.Add(controlbox_sizer, (3, 0), (1, 1),
408            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
409
410        self.SetSizer(vbox)
Note: See TracBrowser for help on using the repository browser.