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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 2a399ca was 2a399ca, checked in by smk78, 7 years ago

Corrected parameter labels in Corfunc perspective
The Corfunc panel was showing 'Long period', whereas in fact the value
displayed was half the Long Period.
Changed label to read 'Long Period / 2'.

  • Property mode set to 100644
File size: 31.6 KB
Line 
1import wx
2import sys
3import os
4import numpy as np
5from wx.lib.scrolledpanel import ScrolledPanel
6from sas.sasgui.guiframe.events import PlotQrangeEvent
7from sas.sasgui.guiframe.events import StatusEvent
8from sas.sasgui.guiframe.events import PanelOnFocusEvent
9from sas.sasgui.guiframe.panel_base import PanelBase
10from sas.sasgui.guiframe.utils import check_float
11from sas.sasgui.guiframe.dataFitting import Data1D
12from sas.sasgui.perspectives.invariant.invariant_widgets import OutputTextCtrl
13from sas.sasgui.perspectives.invariant.invariant_widgets import InvTextCtrl
14from sas.sasgui.perspectives.fitting.basepage import ModelTextCtrl
15from sas.sasgui.perspectives.corfunc.corfunc_state import CorfuncState
16import sas.sasgui.perspectives.corfunc.corfunc
17from sas.sascalc.corfunc.corfunc_calculator import CorfuncCalculator
18from sas.sasgui.guiframe.documentation_window import DocumentationWindow
19from plot_labels import *
20
21OUTPUT_STRINGS = {
22    'max': "Long Period / 2 (A): ",
23    'Lc': "Average Hard Block Thickness (A): ",
24    'dtr': "Average Interface Thickness (A): ",
25    'd0': "Average Core Thickness: ",
26    'A': "Polydispersity: ",
27    'fill': "Local Crystallinity: "
28}
29
30if sys.platform.count("win32") > 0:
31    _STATICBOX_WIDTH = 350
32    PANEL_WIDTH = 400
33    PANEL_HEIGHT = 700
34    FONT_VARIANT = 0
35else:
36    _STATICBOX_WIDTH = 390
37    PANEL_WIDTH = 430
38    PANEL_HEIGHT = 700
39    FONT_VARIANT = 1
40
41class CorfuncPanel(ScrolledPanel,PanelBase):
42    window_name = "Correlation Function"
43    window_caption = "Correlation Function"
44    CENTER_PANE = True
45
46    def __init__(self, parent, data=None, manager=None, *args, **kwds):
47        kwds["size"] = (PANEL_WIDTH, PANEL_HEIGHT)
48        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
49        ScrolledPanel.__init__(self, parent=parent, *args, **kwds)
50        PanelBase.__init__(self, parent)
51        self.SetupScrolling()
52        self.SetWindowVariant(variant=FONT_VARIANT)
53        self._manager = manager
54        # The data with no correction for background values
55        self._data = data # The data to be analysed (corrected fr background)
56        self._extrapolated_data = None # The extrapolated data set
57        # Callable object of class CorfuncCalculator._Interpolator representing
58        # the extrapolated and interpolated data
59        self._extrapolated_fn = None
60        self._transformed_data = None # Fourier trans. of the extrapolated data
61        self._calculator = CorfuncCalculator()
62        self._data_name_box = None # Text box to show name of file
63        self._background_input = None
64        self._qmin_input = None
65        self._qmax1_input = None
66        self._qmax2_input = None
67        self._extrapolate_btn = None
68        self._transform_btn = None
69        self._extract_btn = None
70        self.qmin = 0
71        self.qmax = (0, 0)
72        self.background = 0
73        self.extracted_params = None
74        self.transform_type = 'fourier'
75        self._extrapolation_outputs = {}
76        # Dictionary for saving refs to text boxes used to display output data
77        self._output_boxes = None
78        self.state = None
79        self._do_layout()
80        self._disable_inputs()
81        self.set_state()
82        self._qmin_input.Bind(wx.EVT_TEXT, self._on_enter_input)
83        self._qmax1_input.Bind(wx.EVT_TEXT, self._on_enter_input)
84        self._qmax2_input.Bind(wx.EVT_TEXT, self._on_enter_input)
85        self._qmin_input.Bind(wx.EVT_MOUSE_EVENTS, self._on_click_qrange)
86        self._qmax1_input.Bind(wx.EVT_MOUSE_EVENTS, self._on_click_qrange)
87        self._qmax2_input.Bind(wx.EVT_MOUSE_EVENTS, self._on_click_qrange)
88        self._background_input.Bind(wx.EVT_TEXT, self._on_enter_input)
89
90    def set_state(self, state=None, data=None):
91        """
92        Set the state of the panel. If no state is provided, the panel will
93        be set to the default state.
94
95        :param state: A CorfuncState object
96        :param data: A Data1D object
97        """
98        if state is None:
99            self.state = CorfuncState()
100        else:
101            self.state = state
102        if data is not None:
103            self.state.data = data
104        self.set_data(data, set_qrange=False)
105        if self.state.qmin is not None:
106            self.set_qmin(self.state.qmin)
107        if self.state.qmax is not None and self.state.qmax != (None, None):
108            self.set_qmax(tuple(self.state.qmax))
109        if self.state.background is not None:
110            self.set_background(self.state.background)
111        if self.state.is_extrapolated:
112            self.compute_extrapolation()
113        else:
114            return
115        if self.state.is_transformed:
116            self.transform_type = self.state.transform_type
117            self.compute_transform()
118        else:
119            return
120        if self.state.outputs is not None and self.state.outputs != {}:
121            self.set_extracted_params(self.state.outputs, reset=True)
122
123    def get_state(self):
124        """
125        Return the state of the panel
126        """
127        state = CorfuncState()
128        state.set_saved_state('qmin_tcl', self.qmin)
129        state.set_saved_state('qmax1_tcl', self.qmax[0])
130        state.set_saved_state('qmax2_tcl', self.qmax[1])
131        state.set_saved_state('background_tcl', self.background)
132        state.outputs = self.extracted_params
133        if self._data is not None:
134            state.file = self._data.title
135            state.data = self._data
136        if self._extrapolated_data is not None:
137            state.is_extrapolated = True
138        if self._transformed_data is not None:
139            state.is_transformed = True
140            state.transform_type = self.transform_type
141        self.state = state
142
143        return self.state
144
145    def onSetFocus(self, evt):
146        if evt is not None:
147            evt.Skip()
148        self._validate_inputs()
149
150    def set_data(self, data=None, set_qrange=True):
151        """
152        Update the GUI to reflect new data that has been loaded in
153
154        :param data: The data that has been loaded
155        """
156        if data is None:
157            self._disable_inputs()
158            # Reset outputs
159            self.set_extracted_params(reset=True)
160            self.set_extrapolation_params()
161            self._data = None
162            return
163        self._enable_inputs()
164        self._transform_btn.Disable()
165        self._extract_btn.Disable()
166        self._data_name_box.SetValue(str(data.title))
167        self._data = data
168        self._calculator.set_data(data)
169        # Reset the outputs
170        self.set_extracted_params(None, reset=True)
171        if self._manager is not None:
172            self._manager.clear_data()
173            self._manager.show_data(self._data, IQ_DATA_LABEL, reset=True)
174
175        if set_qrange:
176            lower = data.x[-1]*0.05
177            upper1 = data.x[-1] - lower*5
178            upper2 = data.x[-1]
179            self.set_qmin(lower)
180            self.set_qmax((upper1, upper2))
181            self._compute_background()
182
183    def get_data(self):
184        return self._data
185
186    def radio_changed(self, event=None):
187        """
188        Called when the "Transform type" radio button are changed
189        """
190        if event is not None:
191            self.transform_type = event.GetEventObject().GetName()
192
193    def compute_extrapolation(self, event=None):
194        """
195        Compute and plot the extrapolated data.
196        Called when Extrapolate button is pressed.
197        """
198        if not self._validate_inputs:
199            msg = "Invalid Q range entered."
200            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
201            return
202
203        warning_msg = ""
204        if self.background < 0:
205            warning_msg += "Negative background value entered."
206        if any((self._data.y - self.background) < 0):
207            if warning_msg != "":
208                warning_msg += "\n"
209            warning_msg += "Background value results in negative Intensity values."
210        if warning_msg != "":
211            self._background_input.SetBackgroundColour('yellow')
212            wx.PostEvent(self._manager.parent, StatusEvent(status=warning_msg, info='warning'))
213        else:
214            self._background_input.SetBackgroundColour(wx.WHITE)
215        self._background_input.Refresh()
216
217        self._calculator.set_data(self._data)
218        self._calculator.lowerq = self.qmin
219        self._calculator.upperq = self.qmax
220        self._calculator.background = self.background
221
222        try:
223            params, self._extrapolated_data, self._extrapolated_fn = \
224                self._calculator.compute_extrapolation()
225        except Exception as e:
226            msg = "Error extrapolating data:\n"
227            msg += str(e)
228            wx.PostEvent(self._manager.parent,
229                StatusEvent(status=msg, info="error"))
230            self._transform_btn.Disable()
231            return
232        self._manager.show_data(self._extrapolated_data, IQ_EXTRAPOLATED_DATA_LABEL)
233        # Update state of the GUI
234        self._transform_btn.Enable()
235        self._extract_btn.Disable()
236        self.set_extracted_params(reset=True)
237        self.set_extrapolation_params(params)
238
239    def compute_transform(self, event=None):
240        """
241        Compute and plot the transformed data.
242        Called when Transform button is pressed.
243        """
244        if not self._calculator.transform_isrunning():
245            self._calculator.compute_transform(self._extrapolated_data,
246                self.transform_type, background=self.background,
247                completefn=self.transform_complete,
248                updatefn=self.transform_update)
249
250            self._transform_btn.SetLabel("Stop Transform")
251        else:
252            self._calculator.stop_transform()
253            self.transform_update("Transform cancelled.")
254            self._transform_btn.SetLabel("Transform")
255
256    def transform_update(self, msg=""):
257        """
258        Called from FourierThread to update on status of calculation
259        """
260        wx.PostEvent(self._manager.parent,
261            StatusEvent(status=msg))
262
263    def transform_complete(self, transforms=None):
264        """
265        Called from FourierThread when calculation has completed
266        """
267        self._transform_btn.SetLabel("Transform")
268        if transforms is None:
269            msg = "Error calculating Transform."
270            if self.transform_type == 'hilbert':
271                msg = "Not yet implemented"
272            wx.PostEvent(self._manager.parent,
273                StatusEvent(status=msg, info="Error"))
274            self._extract_btn.Disable()
275            return
276
277        self._transformed_data = transforms
278        (transform1, transform3, idf) = transforms
279        plot_x = transform1.x[transform1.x <= 200]
280        plot_y = transform1.y[transform1.x <= 200]
281        self._manager.show_data(Data1D(plot_x, plot_y), TRANSFORM_LABEL1)
282        # No need to shorten gamma3 as it's only calculated up to x=200
283        self._manager.show_data(transform3, TRANSFORM_LABEL3)
284
285        plot_x = idf.x[idf.x <= 200]
286        plot_y = idf.y[idf.x <= 200]
287        self._manager.show_data(Data1D(plot_x, plot_y), IDF_LABEL)
288
289        # Only enable extract params button if a fourier trans. has been done
290        if self.transform_type == 'fourier':
291            self._extract_btn.Enable()
292        else:
293            self._extract_btn.Disable()
294
295    def extract_parameters(self, event=None):
296        """
297        Called when "Extract Parameters" is clicked
298        """
299        try:
300            params = self._calculator.extract_parameters(self._transformed_data[0])
301        except:
302            params = None
303        if params is None:
304            msg = "Error extracting parameters."
305            wx.PostEvent(self._manager.parent,
306                StatusEvent(status=msg, info="Error"))
307            return
308        self.set_extracted_params(params)
309
310    def on_help(self, event=None):
311        """
312        Show the corfunc documentation
313        """
314        tree_location = "user/sasgui/perspectives/corfunc/corfunc_help.html"
315        doc_viewer = DocumentationWindow(self, -1, tree_location, "",
316                                          "Correlation Function Help")
317
318    def get_save_flag(self):
319        if self._data is not None:
320            return True
321        return False
322
323    def on_set_focus(self, event=None):
324        if self._manager.parent is not None:
325            wx.PostEvent(self._manager.parent, PanelOnFocusEvent(panel=self))
326
327    def on_save(self, event=None):
328        """
329        Save corfunc state into a file
330        """
331        # Ask the user the location of the file to write to.
332        path = None
333        default_save_location = os.getcwd()
334        if self._manager.parent is not None:
335            default_save_location = self._manager.parent.get_save_location()
336
337        dlg = wx.FileDialog(self, "Choose a file",
338                            default_save_location, \
339                            self.window_caption, "*.crf", wx.SAVE)
340        if dlg.ShowModal() == wx.ID_OK:
341            path = dlg.GetPath()
342            default_save_location = os.path.dirname(path)
343            if self._manager.parent is not None:
344                self._manager.parent._default_save_location = default_save_location
345        else:
346            return None
347
348        dlg.Destroy()
349        # MAC always needs the extension for saving
350        extens = ".crf"
351        # Make sure the ext included in the file name
352        f_name = os.path.splitext(path)[0] + extens
353        self._manager.state_reader.write(f_name, self._data, self.get_state())
354
355    def save_project(self, doc=None):
356        """
357        Return an XML node containing the state of the panel
358
359        :param doc: Am xml node to attach the project state to (optional)
360        """
361        data = self._data
362        state = self.get_state()
363        if data is not None:
364            new_doc, sasentry = self._manager.state_reader._to_xml_doc(data)
365            new_doc = state.toXML(doc=new_doc, entry_node=sasentry)
366            if new_doc is not None:
367                if doc is not None and hasattr(doc, "firstChild"):
368                    child = new_doc.getElementsByTagName("SASentry")
369                    for item in child:
370                        doc.firstChild.appendChild(item)
371                else:
372                    doc = new_doc
373        return doc
374
375    def set_qmin(self, qmin):
376        self.qmin = qmin
377        self._qmin_input.SetValue(str(qmin))
378
379    def set_qmax(self, qmax):
380        self.qmax = qmax
381        self._qmax1_input.SetValue(str(qmax[0]))
382        self._qmax2_input.SetValue(str(qmax[1]))
383
384    def set_background(self, bg):
385        self.background = bg
386        self._background_input.SetValue(str(bg))
387        self._calculator.background = bg
388
389    def set_extrapolation_params(self, params=None):
390        """
391        Displays the value of the parameters calculated in the extrapolation
392        """
393        if params is None:
394            # Reset outputs
395            for output in self._extrapolation_outputs.values():
396                output.SetValue('-')
397            return
398        for key, value in params.iteritems():
399            output = self._extrapolation_outputs[key]
400            rounded = self._round_sig_figs(value, 6)
401            output.SetValue(rounded)
402
403
404    def set_extracted_params(self, params=None, reset=False):
405        """
406        Displays the values of the parameters extracted from the Fourier
407        transform
408        """
409        self.extracted_params = params
410        error = False
411        if params is None:
412            if not reset: error = True
413            for output in self._output_boxes.values():
414                output.SetValue('-')
415        else:
416            if len(params) < len(OUTPUT_STRINGS):
417                # Not all parameters were calculated
418                error = True
419            for key, value in params.iteritems():
420                rounded = self._round_sig_figs(value, 6)
421                self._output_boxes[key].SetValue(rounded)
422        if error:
423            msg = 'Not all parameters were able to be calculated'
424            wx.PostEvent(self._manager.parent, StatusEvent(
425                status=msg, info='error'))
426
427    def plot_qrange(self, active=None, leftdown=False):
428        if active is None:
429            active = self._qmin_input
430        wx.PostEvent(self._manager.parent, PlotQrangeEvent(
431            ctrl=[self._qmin_input, self._qmax1_input, self._qmax2_input],
432            active=active, id=IQ_DATA_LABEL, is_corfunc=True,
433            group_id=GROUP_ID_IQ_DATA, leftdown=leftdown))
434
435
436    def _compute_background(self, event=None):
437        """
438        Compute the background level based on the position of the upper q bars
439        """
440        if event is not None:
441            event.Skip()
442        self._on_enter_input()
443        try:
444            bg = self._calculator.compute_background(self.qmax)
445            self.set_background(bg)
446        except Exception as e:
447            msg = "Error computing background level:\n"
448            msg += str(e)
449            wx.PostEvent(self._manager.parent,
450                StatusEvent(status=msg, info="error"))
451
452    def _on_enter_input(self, event=None):
453        """
454        Read values from input boxes and save to memory.
455        """
456        if event is not None: event.Skip()
457        if not self._validate_inputs():
458            return
459        self.qmin = float(self._qmin_input.GetValue())
460        new_qmax1 = float(self._qmax1_input.GetValue())
461        new_qmax2 = float(self._qmax2_input.GetValue())
462        self.qmax = (new_qmax1, new_qmax2)
463        self.background = float(self._background_input.GetValue())
464        self._calculator.background = self.background
465        if event is not None:
466            active_ctrl = event.GetEventObject()
467            if active_ctrl == self._background_input:
468                self._manager.show_data(self._data, IQ_DATA_LABEL,
469                    reset=False, active_ctrl=active_ctrl)
470
471    def _on_click_qrange(self, event=None):
472        if event is None:
473            return
474        event.Skip()
475        if not self._validate_inputs(): return
476        self.plot_qrange(active=event.GetEventObject(),
477            leftdown=event.LeftDown())
478
479    def _validate_inputs(self):
480        """
481        Check that the values for qmin and qmax in the input boxes are valid
482        """
483        if self._data is None:
484            return False
485        qmin_valid = check_float(self._qmin_input)
486        qmax1_valid = check_float(self._qmax1_input)
487        qmax2_valid = check_float(self._qmax2_input)
488        qmax_valid = qmax1_valid and qmax2_valid
489        background_valid = check_float(self._background_input)
490        msg = ""
491        if (qmin_valid and qmax_valid and background_valid):
492            qmin = float(self._qmin_input.GetValue())
493            qmax1 = float(self._qmax1_input.GetValue())
494            qmax2 = float(self._qmax2_input.GetValue())
495            background = float(self._background_input.GetValue())
496            if not qmin > self._data.x.min():
497                msg = "qmin must be greater than the lowest q value"
498                qmin_valid = False
499            elif qmax2 < qmax1:
500                msg = "qmax1 must be less than qmax2"
501                qmax_valid = False
502            elif qmin > qmax1:
503                msg = "qmin must be less than qmax"
504                qmin_valid = False
505            elif background > self._data.y.max():
506                msg = "background must be less than highest I"
507                background_valid = False
508        if not qmin_valid:
509            self._qmin_input.SetBackgroundColour('pink')
510        if not qmax_valid:
511            self._qmax1_input.SetBackgroundColour('pink')
512            self._qmax2_input.SetBackgroundColour('pink')
513        if not background_valid:
514            self._background_input.SetBackgroundColour('pink')
515            if msg != "":
516                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
517        if (qmin_valid and qmax_valid and background_valid):
518            self._qmin_input.SetBackgroundColour(wx.WHITE)
519            self._qmax1_input.SetBackgroundColour(wx.WHITE)
520            self._qmax2_input.SetBackgroundColour(wx.WHITE)
521            self._background_input.SetBackgroundColour(wx.WHITE)
522        self._qmin_input.Refresh()
523        self._qmax1_input.Refresh()
524        self._qmax2_input.Refresh()
525        self._background_input.Refresh()
526        return (qmin_valid and qmax_valid and background_valid)
527
528    def _do_layout(self):
529        """
530        Draw the window content
531        """
532        vbox = wx.GridBagSizer(0,0)
533
534        # I(q) data box
535        databox = wx.StaticBox(self, -1, "I(Q) Data Source")
536        databox_sizer = wx.StaticBoxSizer(databox, wx.VERTICAL)
537
538        file_sizer = wx.GridBagSizer(5, 5)
539
540        y = 0
541
542        file_name_label = wx.StaticText(self, -1, "Name:")
543        file_sizer.Add(file_name_label, (0, 0), (1, 1),
544            wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
545
546        self._data_name_box = OutputTextCtrl(self, -1,
547            size=(300,20))
548        file_sizer.Add(self._data_name_box, (0, 1), (1, 1),
549            wx.CENTER | wx.ADJUST_MINSIZE, 15)
550
551        file_sizer.AddSpacer((1, 25), pos=(0,2))
552        databox_sizer.Add(file_sizer, wx.TOP, 15)
553
554        vbox.Add(databox_sizer, (y, 0), (1, 1),
555            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE | wx.TOP, 15)
556        y += 1
557
558        # Parameters
559        qbox = wx.StaticBox(self, -1, "Input Parameters")
560        qbox_sizer = wx.StaticBoxSizer(qbox, wx.VERTICAL)
561        qbox_sizer.SetMinSize((_STATICBOX_WIDTH, 75))
562
563        q_sizer = wx.GridBagSizer(5, 5)
564
565        # Explanation
566        explanation_txt = ("Corfunc will use all values in the lower range for"
567            " Guinier back extrapolation, and all values in the upper range "
568            "for Porod forward extrapolation.")
569        explanation_label = wx.StaticText(self, -1, explanation_txt,
570            size=(_STATICBOX_WIDTH, 60))
571
572        q_sizer.Add(explanation_label, (0,0), (1,4), wx.LEFT | wx.EXPAND, 5)
573
574        qrange_label = wx.StaticText(self, -1, "Q Range:", size=(50,20))
575        q_sizer.Add(qrange_label, (1,0), (1,1), wx.LEFT | wx.EXPAND, 5)
576
577        # Lower Q Range
578        qmin_label = wx.StaticText(self, -1, "Lower:", size=(50,20))
579        qmin_dash_label = wx.StaticText(self, -1, "-", size=(10,20),
580            style=wx.ALIGN_CENTER_HORIZONTAL)
581
582        qmin_lower = OutputTextCtrl(self, -1, size=(75, 20), value="0.0")
583        self._qmin_input = ModelTextCtrl(self, -1, size=(75, 20),
584                        style=wx.TE_PROCESS_ENTER, name='qmin_input',
585                        text_enter_callback=self._on_enter_input)
586        self._qmin_input.SetToolTipString(("Values with q < qmin will be used "
587            "for Guinier back extrapolation"))
588
589        q_sizer.Add(qmin_label, (2, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
590        q_sizer.Add(qmin_lower, (2, 1), (1, 1), wx.LEFT, 5)
591        q_sizer.Add(qmin_dash_label, (2, 2), (1, 1), wx.CENTER | wx.EXPAND, 5)
592        q_sizer.Add(self._qmin_input, (2, 3), (1, 1), wx.LEFT, 5)
593
594        # Upper Q range
595        qmax_tooltip = ("Values with qmax1 < q < qmax2 will be used for Porod"
596            " forward extrapolation")
597
598        qmax_label = wx.StaticText(self, -1, "Upper:", size=(50,20))
599        qmax_dash_label = wx.StaticText(self, -1, "-", size=(10,20),
600            style=wx.ALIGN_CENTER_HORIZONTAL)
601
602        self._qmax1_input = ModelTextCtrl(self, -1, size=(75, 20),
603            style=wx.TE_PROCESS_ENTER, name="qmax1_input",
604            text_enter_callback=self._on_enter_input)
605        self._qmax1_input.SetToolTipString(qmax_tooltip)
606        self._qmax2_input = ModelTextCtrl(self, -1, size=(75, 20),
607            style=wx.TE_PROCESS_ENTER, name="qmax2_input",
608            text_enter_callback=self._on_enter_input)
609        self._qmax2_input.SetToolTipString(qmax_tooltip)
610
611        q_sizer.Add(qmax_label, (3, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
612        q_sizer.Add(self._qmax1_input, (3, 1), (1, 1), wx.LEFT, 5)
613        q_sizer.Add(qmax_dash_label, (3, 2), (1, 1), wx.CENTER | wx.EXPAND, 5)
614        q_sizer.Add(self._qmax2_input, (3,3), (1, 1), wx.LEFT, 5)
615
616        qbox_sizer.Add(q_sizer, wx.TOP, 0)
617
618        vbox.Add(qbox_sizer, (y, 0), (1, 1),
619            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
620        y += 1
621
622        extrapolation_box = wx.StaticBox(self, -1, "Extrapolation Parameters")
623        extrapolation_sizer = wx.StaticBoxSizer(extrapolation_box, wx.VERTICAL)
624        params_sizer = wx.GridBagSizer(5, 5)
625
626        guinier_label = wx.StaticText(self, -1, "Guinier:")
627        params_sizer.Add(guinier_label, (0, 0), (1,1),
628            wx.ALL | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
629
630        a_label = wx.StaticText(self, -1, "A: ")
631        params_sizer.Add(a_label, (1, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
632
633        a_output = OutputTextCtrl(self, wx.NewId(),
634            value="-", style=wx.ALIGN_CENTER_HORIZONTAL)
635        params_sizer.Add(a_output, (1, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
636        self._extrapolation_outputs['A'] = a_output
637
638        b_label = wx.StaticText(self, -1, "B: ")
639        params_sizer.Add(b_label, (2, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
640
641        b_output = OutputTextCtrl(self, wx.NewId(),
642            value="-", style=wx.ALIGN_CENTER_HORIZONTAL)
643        params_sizer.Add(b_output, (2, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
644        self._extrapolation_outputs['B'] = b_output
645
646        porod_label = wx.StaticText(self, -1, "Porod: ")
647        params_sizer.Add(porod_label, (0, 2), (1, 1),
648            wx.ALL | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
649
650        k_label = wx.StaticText(self, -1, "K: ")
651        params_sizer.Add(k_label, (1, 2), (1, 1), wx.LEFT | wx.EXPAND, 15)
652
653        k_output = OutputTextCtrl(self, wx.NewId(),
654            value="-", style=wx.ALIGN_CENTER_HORIZONTAL)
655        params_sizer.Add(k_output, (1, 3), (1, 1), wx.RIGHT | wx.EXPAND, 15)
656        self._extrapolation_outputs['K'] = k_output
657
658        sigma_label = wx.StaticText(self, -1, u'\u03C3: ')
659        params_sizer.Add(sigma_label, (2, 2), (1, 1), wx.LEFT | wx.EXPAND, 15)
660
661        sigma_output = OutputTextCtrl(self, wx.NewId(),
662            value="-", style=wx.ALIGN_CENTER_HORIZONTAL)
663        params_sizer.Add(sigma_output, (2, 3), (1, 1), wx.RIGHT | wx.EXPAND, 15)
664        self._extrapolation_outputs['sigma'] = sigma_output
665
666        bg_label = wx.StaticText(self, -1, "Bg: ")
667        params_sizer.Add(bg_label, (3, 2), (1, 1), wx.LEFT | wx.EXPAND, 15)
668
669        self._background_input = ModelTextCtrl(self, -1, value="0.0",
670            style=wx.TE_PROCESS_ENTER | wx.TE_CENTRE, name='background_input',
671            text_enter_callback=self._on_enter_input)
672        self._background_input.SetToolTipString(("A background value to "
673            "subtract from all intensity values"))
674        params_sizer.Add(self._background_input, (3, 3), (1, 1), wx.RIGHT | wx.EXPAND, 15)
675
676        background_button = wx.Button(self, wx.NewId(), "Calculate Bg",
677            size=(75, -1))
678        background_button.Bind(wx.EVT_BUTTON, self._compute_background)
679        params_sizer.Add(background_button, (4,3), (1, 1), wx.EXPAND | wx.RIGHT, 15)
680
681        extrapolation_sizer.Add(params_sizer)
682        vbox.Add(extrapolation_sizer, (y, 0), (1, 1),
683            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
684        y += 1
685
686        # Transform type
687        transform_box = wx.StaticBox(self, -1, "Transform Type")
688        transform_sizer = wx.StaticBoxSizer(transform_box, wx.VERTICAL)
689
690        radio_sizer = wx.GridBagSizer(5,5)
691
692        fourier_btn = wx.RadioButton(self, -1, "Fourier", name='fourier',
693            style=wx.RB_GROUP)
694        hilbert_btn = wx.RadioButton(self, -1, "Hilbert", name='hilbert')
695
696        fourier_btn.Bind(wx.EVT_RADIOBUTTON, self.radio_changed)
697        hilbert_btn.Bind(wx.EVT_RADIOBUTTON, self.radio_changed)
698
699        radio_sizer.Add(fourier_btn, (0,0), (1,1), wx.LEFT | wx.EXPAND)
700        radio_sizer.Add(hilbert_btn, (0,1), (1,1), wx.RIGHT | wx.EXPAND)
701
702        transform_sizer.Add(radio_sizer, wx.TOP, 0)
703        vbox.Add(transform_sizer, (y, 0), (1, 1),
704            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
705        y += 1
706
707        # Output data
708        outputbox = wx.StaticBox(self, -1, "Output Parameters")
709        outputbox_sizer = wx.StaticBoxSizer(outputbox, wx.VERTICAL)
710
711        output_sizer = wx.GridBagSizer(5, 5)
712
713        self._output_boxes = dict()
714        i = 0
715        for key, value in OUTPUT_STRINGS.iteritems():
716            # Create a label and a text box for each poperty
717            label = wx.StaticText(self, -1, value)
718            output_box = OutputTextCtrl(self, wx.NewId(),
719                value="-", style=wx.ALIGN_CENTER_HORIZONTAL)
720            # Save the ID of each of the text boxes for accessing after the
721            # output data has been calculated
722            self._output_boxes[key] = output_box
723            output_sizer.Add(label, (i, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
724            output_sizer.Add(output_box, (i, 2), (1, 1),
725                wx.RIGHT | wx.EXPAND, 15)
726            i += 1
727
728        outputbox_sizer.Add(output_sizer, wx.TOP, 0)
729
730        vbox.Add(outputbox_sizer, (y, 0), (1, 1),
731            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
732        y += 1
733
734        # Controls
735        controlbox = wx.StaticBox(self, -1, "Controls")
736        controlbox_sizer = wx.StaticBoxSizer(controlbox, wx.VERTICAL)
737
738        controls_sizer = wx.BoxSizer(wx.VERTICAL)
739
740        self._extrapolate_btn = wx.Button(self, wx.NewId(), "Extrapolate")
741        self._transform_btn = wx.Button(self, wx.NewId(), "Transform")
742        self._extract_btn = wx.Button(self, wx.NewId(), "Compute Parameters")
743        help_btn = wx.Button(self, -1, "HELP")
744
745        self._transform_btn.Disable()
746        self._extract_btn.Disable()
747
748        self._extrapolate_btn.Bind(wx.EVT_BUTTON, self.compute_extrapolation)
749        self._transform_btn.Bind(wx.EVT_BUTTON, self.compute_transform)
750        self._extract_btn.Bind(wx.EVT_BUTTON, self.extract_parameters)
751        help_btn.Bind(wx.EVT_BUTTON, self.on_help)
752
753        controls_sizer.Add(self._extrapolate_btn, wx.CENTER | wx.EXPAND)
754        controls_sizer.Add(self._transform_btn, wx.CENTER | wx.EXPAND)
755        controls_sizer.Add(self._extract_btn, wx.CENTER | wx.EXPAND)
756        controls_sizer.Add(help_btn, wx.CENTER | wx.EXPAND)
757
758        controlbox_sizer.Add(controls_sizer, wx.TOP | wx.EXPAND, 0)
759        vbox.Add(controlbox_sizer, (y, 0), (1, 1),
760            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
761
762
763        self.SetSizer(vbox)
764
765    def _disable_inputs(self):
766        """
767        Disable all input fields
768        """
769        self._qmin_input.Disable()
770        self._qmax1_input.Disable()
771        self._qmax2_input.Disable()
772        self._background_input.Disable()
773        self._extrapolate_btn.Disable()
774
775    def _enable_inputs(self):
776        """
777        Enable all input fields
778        """
779        self._qmin_input.Enable()
780        self._qmax1_input.Enable()
781        self._qmax2_input.Enable()
782        self._background_input.Enable()
783        self._extrapolate_btn.Enable()
784
785    def _round_sig_figs(self, x, sigfigs):
786        """
787        Round a number to a given number of significant figures.
788
789        :param x: The value to round
790        :param sigfigs: How many significant figures to round to
791        :return rounded_str: x rounded to the given number of significant
792            figures, as a string
793        """
794        rounded_str = ""
795        try:
796            # Index of first significant digit
797            significant_digit = -int(np.floor(np.log10(np.abs(x))))
798
799            if np.abs(significant_digit > 4):
800                # Use scientific notation if x > 1e5 or x < 1e4
801                rounded_str = "{1:.{0}E}".format(sigfigs-1, x)
802            else:
803                # Format as a standard decimal
804                # Number of digits required for correct number of sig figs
805                digits = significant_digit + (sigfigs - 1)
806                rounded = np.round(x, decimals=digits)
807                rounded_str = "{1:.{0}f}".format(sigfigs -1  + significant_digit,
808                    rounded)
809        except:
810            # Method for finding significant_digit fails if x is 0 (since log10(0)=inf)
811            if x == 0.0:
812                rounded_str = "0.0"
813            else:
814                rounded_str = "-"
815
816        return rounded_str
Note: See TracBrowser for help on using the repository browser.