source: sasview/prview/perspectives/pr/explore_dialog.py @ 6dc9ad8

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 6dc9ad8 was 96c430d, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

prview: build script and text control fix

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