source: sasview/src/sas/perspectives/pr/inversion_panel.py @ 5e880fe1

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.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 5e880fe1 was 3d250da3, checked in by Doucet, Mathieu <doucetm@…>, 10 years ago

Re #397 Removing breaking EVT_KILL_FOCUS callback.

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