source: sasview/src/sas/sasgui/perspectives/pr/inversion_panel.py @ a0e6b1b

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since a0e6b1b was a0e6b1b, checked in by lewis, 7 years ago

Esnure background value is saved/loaded when analysis is saved/loaded

  • Property mode set to 100644
File size: 40.3 KB
RevLine 
[18b7ecb9]1#!/usr/bin/env python
2
3# version
4__id__ = "$Id: aboutdialog.py 1193 2007-05-03 17:29:59Z dmitriy $"
5__revision__ = "$Revision: 1193 $"
6
7import wx
8import os
9import sys
10import logging
11from wx.lib.scrolledpanel import ScrolledPanel
12from sas.sasgui.guiframe.events import StatusEvent
13from sas.sasgui.guiframe.panel_base import PanelBase
14from inversion_state import InversionState
15from pr_widgets import PrTextCtrl
16from pr_widgets import DataFileTextCtrl
17from pr_widgets import OutputTextCtrl
18from sas.sasgui.guiframe.documentation_window import DocumentationWindow
19
[463e7ffc]20logger = logging.getLogger(__name__)
[c155a16]21
[18b7ecb9]22if sys.platform.count("win32") > 0:
23    FONT_VARIANT = 0
24else:
25    FONT_VARIANT = 1
26
27class InversionControl(ScrolledPanel, PanelBase):
28    """
29    """
30    window_name = 'pr_control'
31    window_caption = "P(r) control panel"
32    CENTER_PANE = True
33
34    # Figure of merit parameters [default]
35
36    ## Oscillation parameters (sin function = 1.1)
37    oscillation_max = 1.5
38
39    def __init__(self, parent, id=-1, plots=None, **kwargs):
40        """
41        """
42        ScrolledPanel.__init__(self, parent, id=id, **kwargs)
43        PanelBase.__init__(self, parent)
44        self.SetupScrolling()
45        #Set window's font size
46        self.SetWindowVariant(variant=FONT_VARIANT)
47        self._set_analysis(False)
48
49        self.plots = plots
50        self.radio_buttons = {}
51        self.parent = parent.parent
52
53        ## Data file TextCtrl
54        self.data_file = None
55        self.plot_data = None
56        self.nfunc_ctl = None
57        self.alpha_ctl = None
58        self.dmax_ctl = None
59        self.time_ctl = None
60        self.chi2_ctl = None
61        self.osc_ctl = None
62        self.file_radio = None
63        self.plot_radio = None
64        self.label_sugg = None
65        self.qmin_ctl = None
66        self.qmax_ctl = None
67        self.swidth_ctl = None
68        self.sheight_ctl = None
69
70        self.rg_ctl = None
71        self.iq0_ctl = None
[5c3c310]72        self.bck_value = None
73        self.bck_est_ctl = None
74        self.bck_man_ctl = None
75        self.has_bck = True
76        self.bck_input = None
[18b7ecb9]77        self.bck_ctl = None
78
79        # TextCtrl for fraction of positive P(r)
80        self.pos_ctl = None
81
82        # TextCtrl for fraction of 1 sigma positive P(r)
83        self.pos_err_ctl = None
84
85        ## Estimates
86        self.alpha_estimate_ctl = None
87        self.nterms_estimate_ctl = None
88        ## D_max distance explorator
89        self.distance_explorator_ctl = None
90        ## Data manager
91        self._manager = None
92        ## Default file location for save
93        self._default_save_location = os.getcwd()
94        if self.parent is not None:
95            self._default_save_location = \
96                        self.parent._default_save_location
97
98        # Default width
99        self._default_width = 350
100        self._do_layout()
101
102    def __setattr__(self, name, value):
103        """
104        Allow direct hooks to text boxes
105        """
106        if name == 'nfunc':
107            self.nfunc_ctl.SetValue(str(int(value)))
108        elif name == 'd_max':
109            self.dmax_ctl.SetValue(str(value))
110        elif name == 'alpha':
111            self.alpha_ctl.SetValue(str(value))
112        elif name == 'chi2':
113            self.chi2_ctl.SetValue("%-5.2g" % value)
114        elif name == 'bck':
115            self.bck_ctl.SetValue("%-5.2g" % value)
116        elif name == 'q_min':
117            self.qmin_ctl.SetValue("%-5.2g" % value)
118        elif name == 'q_max':
119            self.qmax_ctl.SetValue("%-5.2g" % value)
120        elif name == 'elapsed':
121            self.time_ctl.SetValue("%-5.2g" % value)
122        elif name == 'rg':
123            self.rg_ctl.SetValue("%-5.2g" % value)
124        elif name == 'iq0':
125            self.iq0_ctl.SetValue("%-5.2g" % value)
126        elif name == 'oscillation':
127            self.osc_ctl.SetValue("%-5.2g" % value)
128        elif name == 'slit_width':
129            self.swidth_ctl.SetValue("%-5.2g" % value)
130        elif name == 'slit_height':
131            self.sheight_ctl.SetValue("%-5.2g" % value)
132        elif name == 'positive':
133            self.pos_ctl.SetValue("%-5.2g" % value)
134        elif name == 'pos_err':
135            self.pos_err_ctl.SetValue("%-5.2g" % value)
136        elif name == 'alpha_estimate':
137            self.alpha_estimate_ctl.SetToolTipString("Click to accept value.")
138            self.alpha_estimate_ctl.Enable(True)
139            self.alpha_estimate_ctl.SetLabel("%-3.1g" % value)
140            #self.alpha_estimate_ctl.Show()
141            #self.label_sugg.Show()
142        elif name == 'nterms_estimate':
143            self.nterms_estimate_ctl.SetToolTipString("Click to accept value.")
144            self.nterms_estimate_ctl.Enable(True)
145            self.nterms_estimate_ctl.SetLabel("%-g" % value)
146        elif name == 'plotname':
147            self.plot_data.SetValue(str(value))
148            self._on_pars_changed(None)
149        elif name == 'datafile':
150            self.plot_data.SetValue(str(value))
151            self._on_pars_changed(None)
152        else:
153            wx.Panel.__setattr__(self, name, value)
154
155    def __getattr__(self, name):
156        """
157        Allow direct hooks to text boxes
158        """
159        if name == 'nfunc':
160            try:
161                return int(self.nfunc_ctl.GetValue())
162            except:
163                return -1
164        elif name == 'd_max':
165            try:
166                return self.dmax_ctl.GetValue()
167            except:
168                return -1.0
169        elif name == 'alpha':
170            try:
171                return self.alpha_ctl.GetValue()
172            except:
173                return -1.0
174        elif name == 'chi2':
175            try:
176                return float(self.chi2_ctl.GetValue())
177            except:
178                return None
179        elif name == 'bck':
180            try:
181                return float(self.bck_ctl.GetValue())
182            except:
183                return None
184        elif name == 'q_min':
185            try:
186                return float(self.qmin_ctl.GetValue())
187            except:
188                return 0.0
189        elif name == 'q_max':
190            try:
191                return float(self.qmax_ctl.GetValue())
192            except:
193                return 0.0
194        elif name == 'elapsed':
195            try:
196                return float(self.time_ctl.GetValue())
197            except:
198                return None
199        elif name == 'rg':
200            try:
201                return float(self.rg_ctl.GetValue())
202            except:
203                return None
204        elif name == 'iq0':
205            try:
206                return float(self.iq0_ctl.GetValue())
207            except:
208                return None
209        elif name == 'oscillation':
210            try:
211                return float(self.osc_ctl.GetValue())
212            except:
213                return None
214        elif name == 'slit_width':
215            try:
216                return float(self.swidth_ctl.GetValue())
217            except:
218                return None
219        elif name == 'slit_height':
220            try:
221                return float(self.sheight_ctl.GetValue())
222            except:
223                return None
224        elif name == 'pos':
225            try:
226                return float(self.pos_ctl.GetValue())
227            except:
228                return None
229        elif name == 'pos_err':
230            try:
231                return float(self.pos_err_ctl.GetValue())
232            except:
233                return None
234        elif name == 'alpha_estimate':
235            try:
236                return float(self.alpha_estimate_ctl.GetLabel())
237            except:
238                return None
239        elif name == 'nterms_estimate':
240            try:
241                return int(self.nterms_estimate_ctl.GetLabel())
242            except:
243                return None
244        elif name == 'plotname':
245            return self.plot_data.GetValue()
246        elif name == 'datafile':
247            return self.plot_data.GetValue()
248        else:
249            return wx.Panel.__getattribute__(self, name)
250
251    def save_project(self, doc=None):
252        """
253        return an xml node containing state of the panel
254         that guiframe can write to file
255        """
256        data = self.get_data()
257        state = self.get_state()
258        if data is not None:
259            new_doc = self._manager.state_reader.write_toXML(data, state)
260            if new_doc is not None:
261                if doc is not None and hasattr(doc, "firstChild"):
262                    child = new_doc.getElementsByTagName("SASentry")
263                    for item in child:
264                        doc.firstChild.appendChild(item)
265                else:
266                    doc = new_doc
267        return doc
268
269    def on_save(self, evt=None):
270        """
271        Method used to create a memento of the current state
272
273        :return: state object
274        """
275        # Ask the user the location of the file to write to.
276        path = None
[7432acb]277        if self.parent is not None:
[18b7ecb9]278            self._default_save_location = self.parent._default_save_location
279        dlg = wx.FileDialog(self, "Choose a file",
280                            self._default_save_location,
281                            self.window_caption, "*.prv", wx.SAVE)
282        if dlg.ShowModal() == wx.ID_OK:
283            path = dlg.GetPath()
284            self._default_save_location = os.path.dirname(path)
[7432acb]285            if self.parent is not None:
[18b7ecb9]286                self.parent._default_save_location = self._default_save_location
287        else:
288            return None
289
290        dlg.Destroy()
291
292        state = self.get_state()
293
294        # MAC always needs the extension for saving
295        extens = ".prv"
296        # Make sure the ext included in the file name
297        fName = os.path.splitext(path)[0] + extens
298        self._manager.save_data(filepath=fName, prstate=state)
299
300        return state
301
302    def get_data(self):
303        """
304        """
305        return self._manager.get_data()
306
307    def get_state(self):
308        """
309        Get the current state
310
311        : return: state object
312        """
313        # Construct the state object
314        state = InversionState()
315
316        # Read the panel's parameters
317        flag, alpha, dmax, nfunc, qmin, \
[a0e6b1b]318        qmax, height, width, bck = self._read_pars()
[18b7ecb9]319
320        state.nfunc = nfunc
321        state.d_max = dmax
322        state.alpha = alpha
323        state.qmin = qmin
324        state.qmax = qmax
325        state.width = width
326        state.height = height
327
328        # Data file
329        state.file = self.plot_data.GetValue()
330
331        # Background evaluation checkbox
[a0e6b1b]332        state.estimate_bck = self.has_bck
333        state.bck_value = bck
[18b7ecb9]334
335        # Estimates
336        state.nterms_estimate = self.nterms_estimate
337        state.alpha_estimate = self.alpha_estimate
338
339        # Read the output values
340        state.chi2 = self.chi2
341        state.elapsed = self.elapsed
342        state.osc = self.oscillation
343        state.pos = self.pos
344        state.pos_err = self.pos_err
345        state.rg = self.rg
346        state.iq0 = self.iq0
347        state.bck = self.bck
348
349        return state
350
351    def set_state(self, state):
352        """
353        Set the state of the panel and inversion problem to
354        the state passed as a parameter.
355        Execute the inversion immediately after filling the
356        controls.
357
358        :param state: InversionState object
359        """
360        if state.nfunc is not None:
361            self.nfunc = state.nfunc
362        if state.d_max is not None:
363            self.d_max = state.d_max
364        if state.alpha is not None:
365            self.alpha = state.alpha
366        if state.qmin is not None:
367            self.q_min = state.qmin
368        if state.qmax is not None:
369            self.q_max = state.qmax
370        if state.width is not None:
371            self.slit_width = state.width
372        if state.height is not None:
373            self.slit_height = state.height
374
375        # Data file
376        self.plot_data.SetValue(str(state.file))
377
[a0e6b1b]378        # Background value
379        self.bck_est_ctl.SetValue(state.estimate_bck)
380        self.bck_man_ctl.SetValue(not state.estimate_bck)
381        if not state.estimate_bck:
382            self.bck_input.Enable()
383            self.bck_input.SetValue(str(state.bck_value))
384        self.has_bck = state.estimate_bck
385        self.bck_value = state.bck_value
[18b7ecb9]386
387        # Estimates
388        if state.nterms_estimate is not None:
389            self.nterms_estimate = state.nterms_estimate
390        if state.alpha_estimate is not None:
391            self.alpha_estimate = state.alpha_estimate
392
393
394        # Read the output values
395        if state.chi2 is not None:
396            self.chi2 = state.chi2
397        if state.elapsed is not None:
398            self.elapsed = state.elapsed
399        if state.osc is not None:
400            self.oscillation = state.osc
401        if state.pos is not None:
402            self.positive = state.pos
403        if state.pos_err is not None:
404            self.pos_err = state.pos_err
405        if state.rg is not None:
406            self.rg = state.rg
407        if state.iq0 is not None:
408            self.iq0 = state.iq0
409        if state.bck is not None:
410            self.bck = state.bck
411
412        # We have the data available for serialization
413        self._set_analysis(True)
414
415        # Perform inversion
416        self._on_invert(None)
417
418    def set_manager(self, manager):
419        self._manager = manager
420        if manager is not None:
421            self._set_analysis(False)
422
423    def _do_layout(self):
424        vbox = wx.GridBagSizer(0, 0)
425        iy_vb = 0
426
427        # ----- I(q) data -----
428        databox = wx.StaticBox(self, -1, "I(q) data source")
429
430        boxsizer1 = wx.StaticBoxSizer(databox, wx.VERTICAL)
431        boxsizer1.SetMinSize((self._default_width, 50))
432        pars_sizer = wx.GridBagSizer(5, 5)
433
434        iy = 0
435        self.file_radio = wx.StaticText(self, -1, "Name:")
436        pars_sizer.Add(self.file_radio, (iy, 0), (1, 1),
437                       wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
438
439        self.plot_data = DataFileTextCtrl(self, -1, size=(260, 20))
440
441        pars_sizer.Add(self.plot_data, (iy, 1), (1, 1),
442                       wx.EXPAND | wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 15)
443
[5c3c310]444        radio_sizer = wx.GridBagSizer(5, 5)
445
446        self.bck_est_ctl = wx.RadioButton(self, -1, "Estimate background level",
447            name="estimate_bck", style=wx.RB_GROUP)
448        self.bck_man_ctl = wx.RadioButton(self, -1, "Input manual background level",
449            name="manual_bck")
450
451        self.bck_est_ctl.Bind(wx.EVT_RADIOBUTTON, self._on_bck_changed)
452        self.bck_man_ctl.Bind(wx.EVT_RADIOBUTTON, self._on_bck_changed)
453
454        radio_sizer.Add(self.bck_est_ctl, (0,0), (1,1), wx.LEFT | wx.EXPAND)
455        radio_sizer.Add(self.bck_man_ctl, (0,1), (1,1), wx.RIGHT | wx.EXPAND)
456
[18b7ecb9]457        iy += 1
[5c3c310]458        pars_sizer.Add(radio_sizer, (iy, 0), (1, 2),
[18b7ecb9]459                       wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[5c3c310]460
461        background_label = wx.StaticText(self, -1, "Background: ")
462        self.bck_input = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
463            size=(60, 20), value="0.0")
464        self.bck_input.Disable()
[a0e6b1b]465        self.bck_input.Bind(wx.EVT_TEXT, self._read_pars)
[5c3c310]466        background_units = wx.StaticText(self, -1, "[A^(-1)]", size=(55, 20))
467        iy += 1
468
469        background_sizer = wx.GridBagSizer(5, 5)
470
471        background_sizer.Add(background_label, (0, 0), (1,1),
472            wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 23)
473        background_sizer.Add(self.bck_input, (0, 1), (1,1),
474            wx.LEFT | wx.ADJUST_MINSIZE, 5)
475        background_sizer.Add(background_units, (0, 2), (1,1),
476            wx.LEFT | wx.ADJUST_MINSIZE, 5)
477        pars_sizer.Add(background_sizer, (iy, 0), (1, 2),
478            wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
479
[18b7ecb9]480        boxsizer1.Add(pars_sizer, 0, wx.EXPAND)
481        vbox.Add(boxsizer1, (iy_vb, 0), (1, 1),
482                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE | wx.TOP, 5)
483
484        # ----- Add slit parameters -----
485        if True:
486            sbox = wx.StaticBox(self, -1, "Slit parameters")
487            sboxsizer = wx.StaticBoxSizer(sbox, wx.VERTICAL)
488            sboxsizer.SetMinSize((self._default_width, 20))
489
490            sizer_slit = wx.GridBagSizer(5, 5)
491
492            label_sheight = wx.StaticText(self, -1, "Height", size=(40, 20))
493            label_swidth = wx.StaticText(self, -1, "Width", size=(40, 20))
494            label_sunits2 = wx.StaticText(self, -1, "[A^(-1)]", size=(55, 20))
495            self.sheight_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
496            self.swidth_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
497            hint_msg = "Enter slit height in units of Q or leave blank."
498            self.sheight_ctl.SetToolTipString(hint_msg)
499            hint_msg = "Enter slit width in units of Q or leave blank."
500            self.swidth_ctl.SetToolTipString(hint_msg)
501
502            iy = 0
503            sizer_slit.Add(label_sheight, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
504            sizer_slit.Add(self.sheight_ctl, (iy, 1), (1, 1), wx.LEFT | wx.EXPAND, 5)
505            sizer_slit.Add(label_swidth, (iy, 2), (1, 1), wx.LEFT | wx.EXPAND, 5)
506            sizer_slit.Add(self.swidth_ctl, (iy, 3), (1, 1), wx.LEFT | wx.EXPAND, 5)
507            sizer_slit.Add(label_sunits2, (iy, 4), (1, 1), wx.LEFT | wx.EXPAND, 5)
508
509            sboxsizer.Add(sizer_slit, wx.TOP, 15)
510            iy_vb += 1
511            vbox.Add(sboxsizer, (iy_vb, 0), (1, 1),
512                     wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
513
514        # ----- Q range -----
515        qbox = wx.StaticBox(self, -1, "Q range")
516        qboxsizer = wx.StaticBoxSizer(qbox, wx.VERTICAL)
517        qboxsizer.SetMinSize((self._default_width, 20))
518
519        sizer_q = wx.GridBagSizer(5, 5)
520
521        label_qmin = wx.StaticText(self, -1, "Q min", size=(40, 20))
522        label_qmax = wx.StaticText(self, -1, "Q max", size=(40, 20))
523        label_qunits2 = wx.StaticText(self, -1, "[A^(-1)]", size=(55, 20))
524        self.qmin_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
525        self.qmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
526        hint_msg = "Select a lower bound for Q or leave blank."
527        self.qmin_ctl.SetToolTipString(hint_msg)
528        hint_msg = "Select an upper bound for Q or leave blank."
529        self.qmax_ctl.SetToolTipString(hint_msg)
530        self.qmin_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
531        self.qmax_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
532
533        iy = 0
534        sizer_q.Add(label_qmin, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
535        sizer_q.Add(self.qmin_ctl, (iy, 1), (1, 1), wx.LEFT | wx.EXPAND, 5)
536        sizer_q.Add(label_qmax, (iy, 2), (1, 1), wx.LEFT | wx.EXPAND, 5)
537        sizer_q.Add(self.qmax_ctl, (iy, 3), (1, 1), wx.LEFT | wx.EXPAND, 5)
538        sizer_q.Add(label_qunits2, (iy, 4), (1, 1), wx.LEFT | wx.EXPAND, 5)
539        qboxsizer.Add(sizer_q, wx.TOP, 15)
540
541        iy_vb += 1
542        vbox.Add(qboxsizer, (iy_vb, 0), (1, 1),
543                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
544
545        # ----- Parameters -----
546        parsbox = wx.StaticBox(self, -1, "Parameters")
547        boxsizer2 = wx.StaticBoxSizer(parsbox, wx.VERTICAL)
548        boxsizer2.SetMinSize((self._default_width, 50))
549
550        explanation = "P(r) is found by fitting a set of base functions"
551        explanation += " to I(Q). The minimization involves"
552        explanation += " a regularization term to ensure a smooth P(r)."
553        explanation += " The regularization constant gives the size of that "
554        explanation += "term. The suggested value is the value above which the"
555        explanation += " output P(r) will have only one peak."
556        label_explain = wx.StaticText(self, -1, explanation, size=(280, 90))
557        boxsizer2.Add(label_explain, wx.LEFT | wx.BOTTOM, 5)
558
559        label_nfunc = wx.StaticText(self, -1, "Number of terms")
560        label_nfunc.SetMinSize((120, 20))
561        label_alpha = wx.StaticText(self, -1, "Regularization constant")
562        label_dmax = wx.StaticText(self, -1, "Max distance [A]")
563        self.label_sugg = wx.StaticText(self, -1, "Suggested value")
564
565        self.nfunc_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
566        self.nfunc_ctl.SetToolTipString("Number of terms in the expansion.")
567        self.alpha_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
568        hint_msg = "Control parameter for the size of the regularization term."
569        self.alpha_ctl.SetToolTipString(hint_msg)
570        self.dmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
571        hint_msg = "Maximum distance between any two points in the system."
572        self.dmax_ctl.SetToolTipString(hint_msg)
573        wx_id = wx.NewId()
574        self.alpha_estimate_ctl = wx.Button(self, wx_id, "")
575        self.Bind(wx.EVT_BUTTON, self._on_accept_alpha, id=wx_id)
576        self.alpha_estimate_ctl.Enable(False)
577        self.alpha_estimate_ctl.SetToolTipString("Waiting for estimate...")
578
579        wx_id = wx.NewId()
580        self.nterms_estimate_ctl = wx.Button(self, wx_id, "")
581        #self.nterms_estimate_ctl.Hide()
582        self.Bind(wx.EVT_BUTTON, self._on_accept_nterms, id=wx_id)
583        self.nterms_estimate_ctl.Enable(False)
584
585        self.nterms_estimate_ctl.SetToolTipString("Waiting for estimate...")
586
587        self.nfunc_ctl.Bind(wx.EVT_TEXT, self._read_pars)
588        self.alpha_ctl.Bind(wx.EVT_TEXT, self._read_pars)
589        self.dmax_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
590
591        # Distance explorator
592        wx_id = wx.NewId()
593        self.distance_explorator_ctl = wx.Button(self, wx_id, "Explore")
594        self.Bind(wx.EVT_BUTTON, self._on_explore, id=wx_id)
595
596
597        sizer_params = wx.GridBagSizer(5, 5)
598
599        iy = 0
600        sizer_params.Add(self.label_sugg, (iy, 2), (1, 1), wx.LEFT, 15)
601        iy += 1
602        sizer_params.Add(label_nfunc, (iy, 0), (1, 1), wx.LEFT, 15)
603        sizer_params.Add(self.nfunc_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
604        sizer_params.Add(self.nterms_estimate_ctl, (iy, 2), (1, 1), wx.LEFT, 15)
605        iy += 1
606        sizer_params.Add(label_alpha, (iy, 0), (1, 1), wx.LEFT, 15)
607        sizer_params.Add(self.alpha_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
608        sizer_params.Add(self.alpha_estimate_ctl, (iy, 2), (1, 1), wx.LEFT, 15)
609        iy += 1
610        sizer_params.Add(label_dmax, (iy, 0), (1, 1), wx.LEFT, 15)
611        sizer_params.Add(self.dmax_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
612        sizer_params.Add(self.distance_explorator_ctl, (iy, 2),
613                         (1, 1), wx.LEFT, 15)
614
615        boxsizer2.Add(sizer_params, 0)
616
617        iy_vb += 1
618        vbox.Add(boxsizer2, (iy_vb, 0), (1, 1),
619                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
620
621
622        # ----- Results -----
623        resbox = wx.StaticBox(self, -1, "Outputs")
624        ressizer = wx.StaticBoxSizer(resbox, wx.VERTICAL)
625        ressizer.SetMinSize((self._default_width, 50))
626
627        label_rg = wx.StaticText(self, -1, "Rg")
628        label_rg_unit = wx.StaticText(self, -1, "[A]")
629        label_iq0 = wx.StaticText(self, -1, "I(Q=0)")
630        label_iq0_unit = wx.StaticText(self, -1, "[A^(-1)]")
631        label_bck = wx.StaticText(self, -1, "Background")
632        label_bck_unit = wx.StaticText(self, -1, "[A^(-1)]")
633        self.rg_ctl = OutputTextCtrl(self, -1, size=(60, 20))
634        hint_msg = "Radius of gyration for the computed P(r)."
635        self.rg_ctl.SetToolTipString(hint_msg)
636        self.iq0_ctl = OutputTextCtrl(self, -1, size=(60, 20))
637        hint_msg = "Scattering intensity at Q=0 for the computed P(r)."
638        self.iq0_ctl.SetToolTipString(hint_msg)
639        self.bck_ctl = OutputTextCtrl(self, -1, size=(60, 20))
640        self.bck_ctl.SetToolTipString("Value of estimated constant background.")
641
642        label_time = wx.StaticText(self, -1, "Computation time")
643        label_time_unit = wx.StaticText(self, -1, "secs")
644        label_time.SetMinSize((120, 20))
645        label_chi2 = wx.StaticText(self, -1, "Chi2/dof")
646        label_osc = wx.StaticText(self, -1, "Oscillations")
647        label_pos = wx.StaticText(self, -1, "Positive fraction")
648        label_pos_err = wx.StaticText(self, -1, "1-sigma positive fraction")
649
650        self.time_ctl = OutputTextCtrl(self, -1, size=(60, 20))
651        hint_msg = "Computation time for the last inversion, in seconds."
652        self.time_ctl.SetToolTipString(hint_msg)
653
654        self.chi2_ctl = OutputTextCtrl(self, -1, size=(60, 20))
655        self.chi2_ctl.SetToolTipString("Chi^2 over degrees of freedom.")
656
657        # Oscillation parameter
658        self.osc_ctl = OutputTextCtrl(self, -1, size=(60, 20))
659        hint_msg = "Oscillation parameter. P(r) for a sphere has an "
660        hint_msg += " oscillation parameter of 1.1."
661        self.osc_ctl.SetToolTipString(hint_msg)
662
663        # Positive fraction figure of merit
664        self.pos_ctl = OutputTextCtrl(self, -1, size=(60, 20))
665        hint_msg = "Fraction of P(r) that is positive. "
666        hint_msg += "Theoretically, P(r) is defined positive."
667        self.pos_ctl.SetToolTipString(hint_msg)
668
669        # 1-simga positive fraction figure of merit
670        self.pos_err_ctl = OutputTextCtrl(self, -1, size=(60, 20))
671        message = "Fraction of P(r) that is at least 1 standard deviation"
672        message += " greater than zero.\n"
673        message += "This figure of merit tells you about the size of the "
674        message += "P(r) errors.\n"
675        message += "If it is close to 1 and the other figures of merit are bad,"
676        message += " consider changing the maximum distance."
677        self.pos_err_ctl.SetToolTipString(message)
678
679        sizer_res = wx.GridBagSizer(5, 5)
680
681        iy = 0
682        sizer_res.Add(label_rg, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
683        sizer_res.Add(self.rg_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
684        sizer_res.Add(label_rg_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
685        iy += 1
686        sizer_res.Add(label_iq0, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
687        sizer_res.Add(self.iq0_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
688        sizer_res.Add(label_iq0_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
689        iy += 1
690        sizer_res.Add(label_bck, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
691        sizer_res.Add(self.bck_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
692        sizer_res.Add(label_bck_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
693        iy += 1
694        sizer_res.Add(label_time, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
695        sizer_res.Add(self.time_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
696        sizer_res.Add(label_time_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
697        iy += 1
698        sizer_res.Add(label_chi2, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
699        sizer_res.Add(self.chi2_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
700        iy += 1
701        sizer_res.Add(label_osc, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
702        sizer_res.Add(self.osc_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
703
704        iy += 1
705        sizer_res.Add(label_pos, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
706        sizer_res.Add(self.pos_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
707
708        iy += 1
709        sizer_res.Add(label_pos_err, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
710        sizer_res.Add(self.pos_err_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
711
712        ressizer.Add(sizer_res, 0)
713        iy_vb += 1
714        vbox.Add(ressizer, (iy_vb, 0), (1, 1),
715                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
716
717        # ----- Buttons -----
718        wx_id = wx.NewId()
719        button_ok = wx.Button(self, wx_id, "Compute")
720        button_ok.SetToolTipString("Perform P(r) inversion.")
721        self.Bind(wx.EVT_BUTTON, self._on_invert, id=wx_id)
722
723        self.button_help = wx.Button(self, -1, "HELP")
724        self.button_help.SetToolTipString("Get help on P(r) inversion.")
725        self.button_help.Bind(wx.EVT_BUTTON, self.on_help)
726
727        self._set_reset_flag(True)
728        self._set_save_flag(True)
729        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
730        sizer_button.Add((20, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
731        sizer_button.Add(button_ok, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
732        sizer_button.Add(self.button_help, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
733
734        iy_vb += 1
735        vbox.Add(sizer_button, (iy_vb, 0), (1, 1),
736                 wx.EXPAND | wx.BOTTOM | wx.TOP | wx.RIGHT, 10)
737
738        self.Bind(wx.EVT_TEXT_ENTER, self._on_invert)
739
740        self.SetSizer(vbox)
741
742    def _on_accept_alpha(self, evt):
743        """
744        User has accepted the estimated alpha,
745        set it as part of the input parameters
746        """
747        try:
748            alpha = self.alpha_estimate_ctl.GetLabel()
749            # Check that we have a number
750            float(alpha)
751            self.alpha_ctl.SetValue(alpha)
752        except ValueError:
[c155a16]753            logger.error("InversionControl._on_accept_alpha got a value that was not a number: %s" % alpha )
[18b7ecb9]754        except:
755            # No estimate or bad estimate, either do nothing
[c155a16]756            logger.error("InversionControl._on_accept_alpha: %s" % sys.exc_value)
[18b7ecb9]757
758    def _on_accept_nterms(self, evt):
759        """
760        User has accepted the estimated number of terms,
761        set it as part of the input parameters
762        """
763        try:
764            nterms = self.nterms_estimate_ctl.GetLabel()
765            # Check that we have a number
766            float(nterms)
767            self.nfunc_ctl.SetValue(nterms)
768        except ValueError:
[c155a16]769            logger.error("InversionControl._on_accept_nterms got a value that was not a number: %s" % nterms )
[18b7ecb9]770        except:
771            # No estimate or bad estimate, either do nothing
[c155a16]772            logger.error("InversionControl._on_accept_nterms: %s" % sys.exc_value)
[18b7ecb9]773
774    def clear_panel(self):
775        """
776        """
777        self.plot_data.SetValue("")
778        self.on_reset(event=None)
779
780    def on_reset(self, event=None):
781        """
782        Resets inversion parameters
783        """
784        self.nfunc = self._manager.DEFAULT_NFUNC
785        self.d_max = self._manager.DEFAULT_DMAX
786        self.alpha = self._manager.DEFAULT_ALPHA
787        self.qmin_ctl.SetValue("")
788        self.qmax_ctl.SetValue("")
789        self.time_ctl.SetValue("")
790        self.rg_ctl.SetValue("")
791        self.iq0_ctl.SetValue("")
792        self.bck_ctl.SetValue("")
793        self.chi2_ctl.SetValue("")
794        self.osc_ctl.SetValue("")
795        self.pos_ctl.SetValue("")
796        self.pos_err_ctl.SetValue("")
797        self.alpha_estimate_ctl.Enable(False)
798        self.alpha_estimate_ctl.SetLabel("")
799        self.nterms_estimate_ctl.Enable(False)
800        self.nterms_estimate_ctl.SetLabel("")
801        self._set_analysis(False)
802
803        self._on_pars_changed()
804
[5c3c310]805    def _on_bck_changed(self, evt=None):
806        self.has_bck = self.bck_est_ctl.GetValue()
807        if self.has_bck:
808            self.bck_input.Disable()
809        else:
810            self.bck_input.Enable()
811
[18b7ecb9]812    def _on_pars_changed(self, evt=None):
813        """
814        Called when an input parameter has changed
815        We will estimate the alpha parameter behind the
816        scenes.
817        """
[a0e6b1b]818        flag, alpha, dmax, nfunc, qmin, qmax, height, width, bck = self._read_pars()
[18b7ecb9]819
820        # If the pars are valid, estimate alpha
821        if flag:
822            self.nterms_estimate_ctl.Enable(False)
823            self.alpha_estimate_ctl.Enable(False)
824
825            dataset = self.plot_data.GetValue()
826            if dataset is not None and dataset.strip() != "":
827                self._manager.estimate_plot_inversion(alpha=alpha, nfunc=nfunc,
828                                                      d_max=dmax,
829                                                      q_min=qmin, q_max=qmax,
[5c3c310]830                                                      bck=self.has_bck,
[18b7ecb9]831                                                      height=height,
832                                                      width=width)
833
834    def _read_pars(self, evt=None):
835        """
836        """
837        alpha = 0
838        nfunc = 5
839        dmax = 120
840        qmin = 0
841        qmax = 0
842        height = 0
843        width = 0
[a0e6b1b]844        background = 0
[18b7ecb9]845        flag = True
846        # Read slit height
847        try:
848            height_str = self.sheight_ctl.GetValue()
849            if len(height_str.lstrip().rstrip()) == 0:
850                height = 0
851            else:
852                height = float(height_str)
853                self.sheight_ctl.SetBackgroundColour(wx.WHITE)
854                self.sheight_ctl.Refresh()
855        except:
856            flag = False
857            self.sheight_ctl.SetBackgroundColour("pink")
858            self.sheight_ctl.Refresh()
859
860        # Read slit width
861        try:
862            width_str = self.swidth_ctl.GetValue()
863            if len(width_str.lstrip().rstrip()) == 0:
864                width = 0
865            else:
866                width = float(width_str)
867                self.swidth_ctl.SetBackgroundColour(wx.WHITE)
868                self.swidth_ctl.Refresh()
869        except:
870            flag = False
871            self.swidth_ctl.SetBackgroundColour("pink")
872            self.swidth_ctl.Refresh()
873
874        # Read alpha
875        try:
876            alpha = float(self.alpha_ctl.GetValue())
877            self.alpha_ctl.SetBackgroundColour(wx.WHITE)
878            self.alpha_ctl.Refresh()
879        except:
880            flag = False
881            self.alpha_ctl.SetBackgroundColour("pink")
882            self.alpha_ctl.Refresh()
883
884        # Read d_max
885        try:
886            dmax = float(self.dmax_ctl.GetValue())
887            self.dmax_ctl.SetBackgroundColour(wx.WHITE)
888            self.dmax_ctl.Refresh()
889        except:
890            flag = False
891            self.dmax_ctl.SetBackgroundColour("pink")
892            self.dmax_ctl.Refresh()
893
894        # Read nfunc
895        try:
896            nfunc = int(self.nfunc_ctl.GetValue())
897            npts = self._manager.get_npts()
898            if npts > 0 and nfunc > npts:
899                message = "Number of function terms should be smaller "
900                message += "than the number of points"
901                wx.PostEvent(self._manager.parent, StatusEvent(status=message))
902                raise ValueError, message
903            self.nfunc_ctl.SetBackgroundColour(wx.WHITE)
904            self.nfunc_ctl.Refresh()
905        except:
906            flag = False
907            self.nfunc_ctl.SetBackgroundColour("pink")
908            self.nfunc_ctl.Refresh()
909
910        # Read qmin
911        try:
912            qmin_str = self.qmin_ctl.GetValue()
913            if len(qmin_str.lstrip().rstrip()) == 0:
914                qmin = None
915            else:
916                qmin = float(qmin_str)
917                self.qmin_ctl.SetBackgroundColour(wx.WHITE)
918                self.qmin_ctl.Refresh()
919        except:
920            flag = False
921            self.qmin_ctl.SetBackgroundColour("pink")
922            self.qmin_ctl.Refresh()
923
924        # Read qmax
925        try:
926            qmax_str = self.qmax_ctl.GetValue()
927            if len(qmax_str.lstrip().rstrip()) == 0:
928                qmax = None
929            else:
930                qmax = float(qmax_str)
931                self.qmax_ctl.SetBackgroundColour(wx.WHITE)
932                self.qmax_ctl.Refresh()
933        except:
934            flag = False
935            self.qmax_ctl.SetBackgroundColour("pink")
936            self.qmax_ctl.Refresh()
937
[a0e6b1b]938        # Read background
939        if not self.has_bck:
940            try:
941                bck_str = self.bck_input.GetValue()
942                if len(bck_str.strip()) == 0:
943                    background = 0.0
944                else:
945                    background = float(bck_str)
946                    self.bck_input.SetBackgroundColour(wx.WHITE)
947            except ValueError:
948                background = 0.0
949                self.bck_input.SetBackgroundColour("pink")
950            self.bck_input.Refresh()
951
952        return flag, alpha, dmax, nfunc, qmin, qmax, height, width, background
[18b7ecb9]953
954    def _on_explore(self, evt):
955        """
956        Invoke the d_max exploration dialog
957        """
958        from explore_dialog import ExploreDialog
959        if self._manager._last_pr is not None:
960            pr = self._manager._create_plot_pr()
961            dialog = ExploreDialog(pr, 10, None, -1, "")
962            dialog.Show()
963        else:
964            message = "No data to analyze. Please load a data set to proceed."
965            wx.PostEvent(self._manager.parent, StatusEvent(status=message))
966
967    def _on_invert(self, evt):
968        """
969        Perform inversion
970
971        :param silent: when True, there will be no output for the user
972
973        """
974        # Get the data from the form
975        # Push it to the manager
976
[a0e6b1b]977        flag, alpha, dmax, nfunc, qmin, qmax, height, width, bck = self._read_pars()
[18b7ecb9]978
979        if flag:
980            dataset = self.plot_data.GetValue()
[235f514]981            if dataset is None or len(dataset.strip()) == 0:
[18b7ecb9]982                message = "No data to invert. Select a data set before"
983                message += " proceeding with P(r) inversion."
984                wx.PostEvent(self._manager.parent, StatusEvent(status=message))
985            else:
986                self._manager.setup_plot_inversion(alpha=alpha, nfunc=nfunc,
987                                                   d_max=dmax,
988                                                   q_min=qmin, q_max=qmax,
[5c3c310]989                                                   bck=self.has_bck,
[18b7ecb9]990                                                   height=height,
991                                                   width=width)
992        else:
993            message = "The P(r) form contains invalid values: "
994            message += "please submit it again."
995            wx.PostEvent(self.parent, StatusEvent(status=message))
996
997    def _change_file(self, evt=None, filepath=None, data=None):
998        """
999        Choose a new input file for I(q)
1000        """
[c1d5aea]1001        if self._manager is not None:
[18b7ecb9]1002            self.plot_data.SetValue(str(data.name))
1003            try:
1004                self._manager.show_data(data=data, reset=True)
1005                self._on_pars_changed(None)
1006                self._on_invert(None)
1007                self._set_analysis(True)
1008            except:
1009                msg = "InversionControl._change_file: %s" % sys.exc_value
[c155a16]1010                logger.error(msg)
[18b7ecb9]1011
1012    def on_help(self, event):
1013        """
1014        Bring up the P(r) Documentation whenever
1015        the HELP button is clicked.
1016
1017        Calls DocumentationWindow with the path of the location within the
1018        documentation tree (after /doc/ ....".  Note that when using old
1019        versions of Wx (before 2.9) and thus not the release version of
1020        installers, the help comes up at the top level of the file as
1021        webbrowser does not pass anything past the # to the browser when it is
1022        running "file:///...."
1023
1024    :param evt: Triggers on clicking the help button
1025    """
1026
1027        _TreeLocation = "user/sasgui/perspectives/pr/pr_help.html"
1028        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
1029                                          "P(r) Help")
1030
1031
1032class PrDistDialog(wx.Dialog):
1033    """
1034    Property dialog to let the user change the number
1035    of points on the P(r) plot.
1036    """
1037    def __init__(self, parent, id):
1038        from sas.sascalc.pr.invertor import help
1039        wx.Dialog.__init__(self, parent, id, size=(250, 120))
1040        self.SetTitle("P(r) distribution")
1041
1042
1043        vbox = wx.BoxSizer(wx.VERTICAL)
1044
1045        label_npts = wx.StaticText(self, -1, "Number of points")
1046        self.npts_ctl = PrTextCtrl(self, -1, size=(100, 20))
1047
1048        pars_sizer = wx.GridBagSizer(5, 5)
1049        iy = 0
1050        pars_sizer.Add(label_npts, (iy, 0), (1, 1), wx.LEFT, 15)
1051        pars_sizer.Add(self.npts_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
1052
1053        vbox.Add(pars_sizer, 0, wx.ALL | wx.EXPAND, 15)
1054
1055        static_line = wx.StaticLine(self, -1)
1056        vbox.Add(static_line, 0, wx.EXPAND, 0)
1057
1058        button_ok = wx.Button(self, wx.ID_OK, "OK")
1059        self.Bind(wx.EVT_BUTTON, self._checkValues, button_ok)
1060        button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
1061
1062        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
1063        sizer_button.Add((20, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
1064        sizer_button.Add(button_ok, 0, wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10)
1065        sizer_button.Add(button_cancel, 0,
1066                         wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10)
1067        vbox.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10)
1068
1069        self.SetSizer(vbox)
1070        self.SetAutoLayout(True)
1071
1072        self.Layout()
1073        self.Centre()
1074
1075    def _checkValues(self, event):
1076        """
1077        Check the dialog content.
1078        """
1079        flag = True
1080        try:
1081            int(self.npts_ctl.GetValue())
1082            self.npts_ctl.SetBackgroundColour(wx.WHITE)
1083            self.npts_ctl.Refresh()
1084        except:
1085            flag = False
1086            self.npts_ctl.SetBackgroundColour("pink")
1087            self.npts_ctl.Refresh()
1088        if flag:
1089            event.Skip(True)
1090
1091    def get_content(self):
1092        """
1093        Return the content of the dialog.
1094        At this point the values have already been
1095        checked.
1096        """
1097        value = int(self.npts_ctl.GetValue())
1098        return value
1099
1100    def set_content(self, npts):
1101        """
1102        Initialize the content of the dialog.
1103        """
1104        self.npts_ctl.SetValue("%i" % npts)
Note: See TracBrowser for help on using the repository browser.