source: sasview/inversionview/src/sans/perspectives/pr/explore_dialog.py @ 8b1d759

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 8b1d759 was e16e5c3, checked in by Jae Cho <jhjcho@…>, 13 years ago

handles minor error: unfocus

  • Property mode set to 100644
File size: 16.7 KB
Line 
1
2################################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#
7#See the license text in license.txt
8#
9#copyright 2009, University of Tennessee
10################################################################################
11
12"""
13Dialog panel to explore the P(r) inversion results for a range
14of D_max value. User picks a number of points and a range of
15distances, then can toggle between inversion outputs and see
16their distribution as a function of D_max.
17"""
18   
19
20import wx
21import numpy
22import math
23import logging
24import sys
25
26# Avoid Matplotlib complaining about the lack of legend on the plot
27import warnings
28warnings.simplefilter("ignore")
29
30# Import plotting classes
31from danse.common.plottools.PlotPanel import PlotPanel
32from danse.common.plottools import Data1D as Model1D
33from sans.guiframe.gui_style import GUIFRAME_ID
34from danse.common.plottools.plottables import Graph
35
36from pr_widgets import PrTextCtrl
37
38# Default number of points on the output plot
39DEFAULT_NPTS = 10
40# Default output parameter to plot
41DEFAULT_OUTPUT = 'Chi2/dof'
42
43class OutputPlot(PlotPanel):
44    """
45    Plot panel used to show the selected results as a function
46    of D_max
47    """
48    def __init__(self, d_min, d_max, parent, id = -1, color = None,\
49        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
50        """
51        Initialization. The parameters added to PlotPanel are:
52       
53        :param d_min: Minimum value of D_max to explore
54        :param d_max: Maximum value of D_max to explore
55       
56        """
57        PlotPanel.__init__(self, parent, id = id, style = style, **kwargs)
58
59        self.parent = parent
60        self.min = d_min
61        self.max = d_max
62        self.npts = DEFAULT_NPTS
63
64        step = (self.max-self.min)/(self.npts-1)
65        self.x = numpy.arange(self.min, self.max+step*0.01, step)
66        dx = numpy.zeros(len(self.x))
67        y = numpy.ones(len(self.x))
68        dy = numpy.zeros(len(self.x))
69 
70        # Plot area
71        self.plot = Model1D(self.x, y=y, dy=dy)
72        self.plot.name = DEFAULT_OUTPUT
73        self.plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
74
75        # Graph       
76        self.graph = Graph()
77        self.graph.xaxis("\\rm{D_{max}}", 'A')
78        self.graph.yaxis("\\rm{%s}" % DEFAULT_OUTPUT,"")
79        self.graph.add(self.plot)
80        self.graph.render(self)
81
82    def onContextMenu(self, event):
83        """
84        Default context menu for the plot panel
85       
86        :TODO: Would be nice to add printing and log/linear scales.
87            The current verison of plottools no longer plays well with
88            plots outside of guiframe. Guiframe team needs to fix this.
89        """
90        # Slicer plot popup menu
91        id = wx.NewId()
92        slicerpop = wx.Menu()
93        slicerpop.Append(id, '&Save image', 'Save image as PNG')
94        wx.EVT_MENU(self, id, self.onSaveImage)
95       
96        id = wx.NewId()
97        slicerpop.AppendSeparator()
98        slicerpop.Append(id, '&Reset Graph')
99        wx.EVT_MENU(self, id, self.onResetGraph)
100       
101        pos = event.GetPosition()
102        pos = self.ScreenToClient(pos)
103        self.PopupMenu(slicerpop, pos)
104
105class Results:
106    """
107    Class to hold the inversion output parameters
108    as a function of D_max
109    """
110    def __init__(self):
111        """
112        Initialization. Create empty arrays
113        and dictionary of labels.
114        """
115        # Array of output for each inversion
116        self.chi2 = []
117        self.osc = []
118        self.pos = []
119        self.pos_err = []
120        self.rg = []
121        self.iq0 = []
122        self.bck = []
123        self.d_max = []
124       
125        # Dictionary of outputs
126        self.outputs = {}
127        self.outputs['Chi2/dof'] = ["\chi^2/dof", "a.u.", self.chi2]   
128        self.outputs['Oscillation parameter'] = ["Osc", "a.u.", self.osc]   
129        self.outputs['Positive fraction'] = ["P^+", "a.u.", self.pos]   
130        self.outputs['1-sigma positive fraction'] = ["P^+_{1\ \sigma}", 
131                                                     "a.u.", self.pos_err]   
132        self.outputs['Rg'] = ["R_g", "A", self.rg]   
133        self.outputs['I(q=0)'] = ["I(q=0)", "1/A", self.iq0]   
134        self.outputs['Background'] = ["Bck", "1/A", self.bck] 
135       
136class ExploreDialog(wx.Dialog):
137    """
138    The explorer dialog box. This dialog is meant to be
139    invoked by the InversionControl class.
140    """
141   
142    def __init__(self, pr_state, nfunc, *args, **kwds):
143        """
144        Initialization. The parameters added to Dialog are:
145       
146        :param pr_state: sans.pr.invertor.Invertor object
147        :param nfunc: Number of terms in the expansion
148       
149        """
150        kwds["style"] = wx.RESIZE_BORDER|wx.DEFAULT_DIALOG_STYLE
151        wx.Dialog.__init__(self, *args, **kwds)
152       
153        # Initialize Results object
154        self.results = Results()
155       
156        self.pr_state  = pr_state
157        self._default_min = 0.9*self.pr_state.d_max
158        self._default_max = 1.1*self.pr_state.d_max
159        self.nfunc     = nfunc
160       
161        # Control for number of points
162        self.npts_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
163                                   size=(60,20))
164        # Control for the minimum value of D_max
165        self.dmin_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
166                                   size=(60,20))
167        # Control for the maximum value of D_max
168        self.dmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
169                                   size=(60,20))
170
171        # Output selection box for the y axis
172        self.output_box = None
173
174        # Create the plot object
175        self.plotpanel = OutputPlot(self._default_min, self._default_max,
176                                    self, -1, style=wx.RAISED_BORDER)
177       
178        # Create the layout of the dialog
179        self.__do_layout()
180        self.Fit()
181       
182        # Calculate exploration results
183        self._recalc()
184        # Graph the default output curve
185        self._plot_output()
186       
187    class Event:
188        """
189        Class that holds the content of the form
190        """
191        ## Number of points to be plotted
192        npts   = 0
193        ## Minimum value of D_max
194        dmin   = 0
195        ## Maximum value of D_max
196        dmax   = 0
197               
198    def _get_values(self, event=None):
199        """
200        Invoked when the user changes a value of the form.
201        Check that the values are of the right type.
202       
203        :return: ExploreDialog.Event object if the content is good,
204            None otherwise
205        """
206        # Flag to make sure that all values are good
207        flag = True
208       
209        # Empty ExploreDialog.Event content
210        content_event = self.Event()
211       
212        # Read each text control and make sure the type is valid
213        # Let the user know if a type is invalid by changing the
214        # background color of the control.
215        try:
216            content_event.npts = int(self.npts_ctl.GetValue())
217            self.npts_ctl.SetBackgroundColour(wx.WHITE)
218            self.npts_ctl.Refresh()
219        except:
220            flag = False
221            self.npts_ctl.SetBackgroundColour("pink")
222            self.npts_ctl.Refresh()
223           
224        try:
225            content_event.dmin = float(self.dmin_ctl.GetValue())
226            self.dmin_ctl.SetBackgroundColour(wx.WHITE)
227            self.dmin_ctl.Refresh()
228        except:
229            flag = False
230            self.dmin_ctl.SetBackgroundColour("pink")
231            self.dmin_ctl.Refresh()
232       
233        try:
234            content_event.dmax = float(self.dmax_ctl.GetValue())
235            self.dmax_ctl.SetBackgroundColour(wx.WHITE)
236            self.dmax_ctl.Refresh()
237        except:
238            flag = False
239            self.dmax_ctl.SetBackgroundColour("pink")
240            self.dmax_ctl.Refresh()
241       
242        # If the content of the form is valid, return the content,
243        # otherwise return None
244        if flag:
245            if event is not None:
246                event.Skip(True)
247            return content_event
248        else:
249            return None
250   
251    def _plot_output(self, event=None):
252        """
253        Invoked when a new output type is selected for plotting,
254        or when a new computation is finished.
255        """
256        # Get the output type selection
257        output_type = self.output_box.GetString(self.output_box.GetSelection())
258       
259        # If the selected output type is part of the results ojbect,
260        # display the results.
261        # Note: by design, the output type should always be part of the
262        #       results object.
263        if self.results.outputs.has_key(output_type): 
264            self.plotpanel.plot.x = self.results.d_max
265            self.plotpanel.plot.y = self.results.outputs[output_type][2]
266            self.plotpanel.plot.name = '_nolegend_'
267            y_label = "\\rm{%s}" % self.results.outputs[output_type][0]
268            self.plotpanel.graph.yaxis(y_label,
269                                       self.results.outputs[output_type][1])
270           
271            # Redraw
272            self.plotpanel.graph.render(self.plotpanel)
273            self.plotpanel.subplot.figure.canvas.draw_idle()
274        else:
275            msg =  "ExploreDialog: the Results object's dictionary "
276            msg += "does not contain "
277            msg += "the [%s] output type. This must be indicative of "
278            msg += "a change in the " % str(output_type)
279            msg += "ExploreDialog code."
280            logging.error(msg)
281       
282    def __do_layout(self):
283        """
284        Do the layout of the dialog
285        """
286        # Dialog box properties
287        self.SetTitle("D_max Explorer")
288        self.SetSize((600, 595))
289       
290        sizer_main = wx.BoxSizer(wx.VERTICAL)
291        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
292        sizer_params = wx.GridBagSizer(5, 5)
293
294        iy = 0
295        ix = 0
296        label_npts  = wx.StaticText(self, -1, "Npts")
297        sizer_params.Add(label_npts, (iy, ix), (1, 1), 
298                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
299        ix += 1
300        sizer_params.Add(self.npts_ctl,   (iy, ix), (1, 1), 
301                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
302        self.npts_ctl.SetValue("%g" % DEFAULT_NPTS)
303        self.npts_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
304       
305        ix += 1
306        label_dmin   = wx.StaticText(self, -1, "Min Distance [A]")
307        sizer_params.Add(label_dmin, (iy, ix), (1, 1),
308                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
309        ix += 1
310        sizer_params.Add(self.dmin_ctl, (iy, ix), (1, 1),
311                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
312        self.dmin_ctl.SetValue(str(self._default_min))
313        self.dmin_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
314       
315        ix += 1
316        label_dmax = wx.StaticText(self, -1, "Max Distance [A]")
317        sizer_params.Add(label_dmax, (iy, ix), (1, 1),
318                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
319        ix += 1
320        sizer_params.Add(self.dmax_ctl,   (iy, ix), (1, 1),
321                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
322        self.dmax_ctl.SetValue(str(self._default_max))
323        self.dmax_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
324
325
326        # Ouput selection box
327        selection_msg = wx.StaticText(self, -1, "Select a dependent variable:")
328        self.output_box = wx.ComboBox(self, -1)
329        for item in self.results.outputs.keys():
330            self.output_box.Append(item, "")
331        self.output_box.SetStringSelection(DEFAULT_OUTPUT)
332       
333        output_sizer = wx.GridBagSizer(5, 5)
334        output_sizer.Add(selection_msg, (0, 0), (1, 1),
335                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
336        output_sizer.Add(self.output_box, (0, 1), (1, 2),
337                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
338       
339        wx.EVT_COMBOBOX(self.output_box, -1, self._plot_output) 
340        sizer_main.Add(output_sizer, 0, wx.EXPAND | wx.ALL, 10)
341       
342        sizer_main.Add(self.plotpanel, 0, wx.EXPAND | wx.ALL, 10)
343        sizer_main.SetItemMinSize(self.plotpanel, 400, 400)
344       
345        sizer_main.Add(sizer_params, 0, wx.EXPAND|wx.ALL, 10)
346        static_line_3 = wx.StaticLine(self, -1)
347        sizer_main.Add(static_line_3, 0, wx.EXPAND, 0)
348       
349        # Bottom area with the close button
350        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
351        button_OK = wx.Button(self, wx.ID_OK, "Close")
352        sizer_button.Add(button_OK, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
353       
354        sizer_main.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
355        self.SetAutoLayout(True)
356        self.SetSizer(sizer_main)
357        self.Layout()
358        self.Centre()
359       
360        # Bind the Enter key to recalculation
361        self.Bind(wx.EVT_TEXT_ENTER, self._recalc)
362
363    def set_plot_unfocus(self):
364        """
365        Not implemented
366        """
367        pass
368   
369    def _recalc(self, event=None):
370        """
371        Invoked when the user changed a value on the form.
372        Process the form and compute the output to be plottted.
373        """
374        # Get the content of the form
375        content = self._get_values()
376        # If the content of the form is invalid, return and do nothing
377        if content is None:
378            return
379       
380        # Results object to store the computation outputs.
381        results = Results()
382       
383        # Loop over d_max values
384        for i in range(content.npts): 
385            temp = (content.dmax - content.dmin)/(content.npts - 1.0)   
386            d = content.dmin + i * temp
387               
388            self.pr_state.d_max = d
389            try:
390                out, cov = self.pr_state.invert(self.nfunc)   
391           
392                # Store results
393                iq0 = self.pr_state.iq0(out)
394                rg = self.pr_state.rg(out)
395                pos = self.pr_state.get_positive(out)
396                pos_err = self.pr_state.get_pos_err(out, cov)
397                osc = self.pr_state.oscillations(out)
398           
399                results.d_max.append(self.pr_state.d_max)
400                results.bck.append(self.pr_state.background)
401                results.chi2.append(self.pr_state.chi2)
402                results.iq0.append(iq0)
403                results.rg.append(rg)
404                results.pos.append(pos)
405                results.pos_err.append(pos_err)
406                results.osc.append(osc)           
407            except:
408                # This inversion failed, skip this D_max value
409                msg = "ExploreDialog: inversion failed "
410                msg += "for D_max=%s\n%s" % (str(d), sys.exc_value)
411                logging.error(msg)
412           
413        self.results = results           
414         
415        # Plot the selected output
416        self._plot_output()   
417           
418##### testing code ############################################################
419"""
420Example: ::
421
422class MyApp(wx.App):
423   
424    #Test application used to invoke the ExploreDialog for testing
425   
426    def OnInit(self):
427        from inversion_state import Reader
428        from sans.pr.invertor import Invertor
429        wx.InitAllImageHandlers()
430       
431        def call_back(state, datainfo=None):
432           
433            #Dummy call-back method used by the P(r)
434            #file reader.
435           
436            print state
437           
438        # Load test data
439        # This would normally be loaded by the application
440        # of the P(r) plug-in.
441        r = Reader(call_back)
442        data = r.read("test_pr_data.prv")
443        pr_state = data.meta_data['prstate']
444       
445        # Create Invertor object for the data
446        # This would normally be assembled by the P(r) plug-in
447        pr = Invertor()
448        pr.d_max = pr_state.d_max
449        pr.alpha = pr_state.alpha
450        pr.q_min = pr_state.qmin
451        pr.q_max = pr_state.qmax
452        pr.x = data.x
453        pr.y = data.y
454        pr.err = data.y*0.1
455        pr.has_bck = False
456        pr.slit_height = pr_state.height
457        pr.slit_width = pr_state.width
458       
459        # Create the dialog
460        dialog = ExploreDialog(pr, 10, None, -1, "")
461        self.SetTopWindow(dialog)
462        dialog.ShowModal()
463        dialog.Destroy()
464
465        return 1
466
467if __name__ == "__main__":
468    app = MyApp(0)
469    app.MainLoop()
470   
471""" 
Note: See TracBrowser for help on using the repository browser.