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

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 c1d5aea was c1d5aea, checked in by andyfaff, 7 years ago

MAINT: use 'x is not None', instead of 'not x is None'.

  • Property mode set to 100644
File size: 47.0 KB
RevLine 
[c1c14ba]1"""
2    P(r) perspective for SasView
3"""
[7116b6e0]4################################################################################
5#This software was developed by the University of Tennessee as part of the
6#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
[c1c14ba]7#project funded by the US National Science Foundation.
[7116b6e0]8#
9#See the license text in license.txt
10#
11#copyright 2009, University of Tennessee
12################################################################################
[9ff861b]13
14
[c1c14ba]15# Make sure the option of saving each curve is available
[f3d51f6]16# Use the I(q) curve as input and compare the output to P(r)
17
[4a5de6f]18import sys
[f3d51f6]19import wx
[aa4b8379]20import logging
[302510b]21import time
[75df58b]22import math
[9a5097c]23import numpy as np
[75df58b]24import pylab
[d85c194]25from sas.sasgui.guiframe.gui_manager import MDIFrame
26from sas.sasgui.guiframe.dataFitting import Data1D
27from sas.sasgui.guiframe.events import NewPlotEvent
28from sas.sasgui.guiframe.events import StatusEvent
29from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
[b699768]30from sas.sascalc.pr.invertor import Invertor
31from sas.sascalc.dataloader.loader import Loader
32import sas.sascalc.dataloader
[a07e72f]33
[c1c14ba]34from pr_widgets import load_error
[d85c194]35from sas.sasgui.guiframe.plugin_base import PluginBase
[f3d51f6]36
[463e7ffc]37logger = logging.getLogger(__name__)
[c155a16]38
[0d88a09]39
[c1c14ba]40PR_FIT_LABEL = r"$P_{fit}(r)$"
41PR_LOADED_LABEL = r"$P_{loaded}(r)$"
42IQ_DATA_LABEL = r"$I_{obs}(q)$"
43IQ_FIT_LABEL = r"$I_{fit}(q)$"
44IQ_SMEARED_LABEL = r"$I_{smeared}(q)$"
[119dc89]45GROUP_ID_IQ_DATA = r"$I_{obs}(q)$"
46GROUP_ID_PR_FIT = r"$P_{fit}(r)$"
47
[aa4b8379]48
49
[3e41f43]50class Plugin(PluginBase):
[7116b6e0]51    """
[4b73c3e]52        P(r) inversion perspective
[7116b6e0]53    """
[b659551]54    DEFAULT_ALPHA = 0.0001
55    DEFAULT_NFUNC = 10
[c1c14ba]56    DEFAULT_DMAX = 140.0
57
[f21d496]58    def __init__(self):
59        PluginBase.__init__(self, name="Pr Inversion")
[f3d51f6]60        ## Simulation window manager
61        self.simview = None
[c1c14ba]62
[f3d51f6]63        ## State data
[c1c14ba]64        self.alpha = self.DEFAULT_ALPHA
65        self.nfunc = self.DEFAULT_NFUNC
[b659551]66        self.max_length = self.DEFAULT_DMAX
[c1c14ba]67        self.q_min = None
68        self.q_max = None
69        self.has_bck = False
[2a92852]70        self.slit_height = 0
[c1c14ba]71        self.slit_width = 0
[f3d51f6]72        ## Remember last plottable processed
[c1c14ba]73        self.last_data = ""
[0bae207]74        self._current_file_data = None
[f3d51f6]75        ## Time elapsed for last computation [sec]
76        # Start with a good default
77        self.elapsed = 0.022
[aa4b8379]78        self.iq_data_shown = False
[c1c14ba]79
[f3d51f6]80        ## Current invertor
[c1c14ba]81        self.invertor = None
82        self.pr = None
83        self.data_id = IQ_DATA_LABEL
[feb21d8]84        # Copy of the last result in case we need to display it.
[c1c14ba]85        self._last_pr = None
86        self._last_out = None
87        self._last_cov = None
[f3d51f6]88        ## Calculation thread
89        self.calc_thread = None
90        ## Estimation thread
91        self.estimation_thread = None
92        ## Result panel
93        self.control_panel = None
94        ## Currently views plottable
95        self.current_plottable = None
[b659551]96        ## Number of P(r) points to display on the output plot
97        self._pr_npts = 51
[feb21d8]98        self._normalize_output = False
99        self._scale_output_unity = False
[c1c14ba]100
[f1181977]101        ## List of added P(r) plots
102        self._added_plots = {}
[c1c14ba]103        self._default_Iq = {}
[91d910d]104        self.list_plot_id = []
[c1c14ba]105
[6f1f129]106        # Associate the inversion state reader with .prv files
107        from inversion_state import Reader
[c1c14ba]108
[0d88a09]109        # Create a CanSAS/Pr reader
110        self.state_reader = Reader(self.set_state)
[6d3d5ff]111        self._extensions = '.prv'
[6f1f129]112        l = Loader()
[410aad8]113        l.associate_file_reader('.prv', self.state_reader)
[b1a9e8b]114        #l.associate_file_reader(".svs", self.state_reader)
[c1c14ba]115
[aa4b8379]116        # Log startup
[c155a16]117        logger.info("Pr(r) plug-in started")
[c1c14ba]118
[91d910d]119    def delete_data(self, data_id):
120        """
121        delete the data association with prview
122        """
123        self.control_panel.clear_panel()
[c1c14ba]124
[6d3d5ff]125    def get_data(self):
126        """
[4b73c3e]127            Returns the current data
[6d3d5ff]128        """
129        return self.current_plottable
[c1c14ba]130
[75fbd17]131    def set_state(self, state=None, datainfo=None):
[6f1f129]132        """
[7116b6e0]133        Call-back method for the inversion state reader.
134        This method is called when a .prv file is loaded.
[c1c14ba]135
[7116b6e0]136        :param state: InversionState object
137        :param datainfo: Data1D object [optional]
[c1c14ba]138
[6f1f129]139        """
[410aad8]140        try:
[75fbd17]141            if datainfo.__class__.__name__ == 'list':
[c03b780]142                if len(datainfo) >= 1:
[75fbd17]143                    data = datainfo[0]
[c03b780]144                else:
145                    data = None
146            else:
147                data = datainfo
[75fbd17]148            if data is None:
[c1c14ba]149                msg = "Pr.set_state: datainfo parameter cannot "
[4b73c3e]150                msg += "be None in standalone mode"
151                raise RuntimeError, msg
[c1c14ba]152
[0d88a09]153            # Ensuring that plots are coordinated correctly
[75fbd17]154            t = time.localtime(data.meta_data['prstate'].timestamp)
[0d88a09]155            time_str = time.strftime("%b %d %H:%M", t)
[c1c14ba]156
[0d88a09]157            # Check that no time stamp is already appended
[75fbd17]158            max_char = data.meta_data['prstate'].file.find("[")
[0d88a09]159            if max_char < 0:
[75fbd17]160                max_char = len(data.meta_data['prstate'].file)
[c1c14ba]161
162            datainfo.meta_data['prstate'].file = \
[4b73c3e]163                data.meta_data['prstate'].file[0:max_char]\
164                + ' [' + time_str + ']'
[c1c14ba]165
[75fbd17]166            data.filename = data.meta_data['prstate'].file
[053c769]167            # TODO:
168            #remove this call when state save all information about the gui data
169            # such as ID , Group_ID, etc...
170            #make self.current_plottable = datainfo directly
[4b73c3e]171            self.current_plottable = self.parent.create_gui_data(data, None)
[229da98]172            self.current_plottable.group_id = data.meta_data['prstate'].file
[c1c14ba]173
[75fbd17]174            # Make sure the user sees the P(r) panel after loading
[c1c14ba]175            #self.parent.set_perspective(self.perspective)
176            self.on_perspective(event=None)
[410aad8]177            # Load the P(r) results
[229da98]178            #state = self.state_reader.get_state()
[c8afcb7]179            data_dict = {self.current_plottable.id:self.current_plottable}
180            self.parent.add_data(data_list=data_dict)
[75fbd17]181            wx.PostEvent(self.parent, NewPlotEvent(plot=self.current_plottable,
[c1c14ba]182                                                   title=self.current_plottable.title))
[410aad8]183            self.control_panel.set_state(state)
184        except:
[c155a16]185            logger.error("prview.set_state: %s" % sys.exc_value)
[f3d51f6]186
[c1c14ba]187
[119a11d]188    def help(self, evt):
189        """
[c1c14ba]190        Show a general help dialog.
191
[7116b6e0]192        :TODO: replace the text with a nice image
[c1c14ba]193
[119a11d]194        """
195        from inversion_panel import HelpDialog
196        dialog = HelpDialog(None, -1)
197        if dialog.ShowModal() == wx.ID_OK:
198            dialog.Destroy()
199        else:
200            dialog.Destroy()
[c1c14ba]201
[f3d51f6]202    def _fit_pr(self, evt):
[7116b6e0]203        """
204        """
[f3d51f6]205        # Generate P(r) for sphere
206        radius = 60.0
[c1c14ba]207        d_max = 2 * radius
208
209        r = pylab.arange(0.01, d_max, d_max / 51.0)
[f3d51f6]210        M = len(r)
[9a5097c]211        y = np.zeros(M)
212        pr_err = np.zeros(M)
[c1c14ba]213
214        total = 0.0
[f3d51f6]215        for j in range(M):
216            value = self.pr_theory(r[j], radius)
[c1c14ba]217            total += value
[f3d51f6]218            y[j] = value
219            pr_err[j] = math.sqrt(y[j])
220
[c1c14ba]221        y = y / total * d_max / len(r)
[f3d51f6]222
223        # Perform fit
224        pr = Invertor()
225        pr.d_max = d_max
226        pr.alpha = 0
227        pr.x = r
228        pr.y = y
229        pr.err = pr_err
230        out, cov = pr.pr_fit()
231        for i in range(len(out)):
232            print "%g +- %g" % (out[i], math.sqrt(cov[i][i]))
[c1c14ba]233
[f3d51f6]234        # Show input P(r)
[a07e72f]235        title = "Pr"
236        new_plot = Data1D(pr.x, pr.y, dy=pr.err)
[f3d51f6]237        new_plot.name = "P_{obs}(r)"
238        new_plot.xaxis("\\rm{r}", 'A')
[c1c14ba]239        new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}")
[e88ebfd]240        new_plot.group_id = "P_{obs}(r)"
[a07e72f]241        new_plot.id = "P_{obs}(r)"
242        new_plot.title = title
[c1c14ba]243        self.parent.update_theory(data_id=self.data_id, theory=new_plot)
[a07e72f]244        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
[f3d51f6]245
246        # Show P(r) fit
247        self.show_pr(out, pr)
[c1c14ba]248
[f3d51f6]249        # Show I(q) fit
[c1c14ba]250        q = pylab.arange(0.001, 0.1, 0.01 / 51.0)
[f3d51f6]251        self.show_iq(out, pr, q)
[c1c14ba]252
[f3d51f6]253    def show_shpere(self, x, radius=70.0, x_range=70.0):
[7116b6e0]254        """
255        """
[f3d51f6]256        # Show P(r)
[9a5097c]257        y_true = np.zeros(len(x))
[f3d51f6]258
259        sum_true = 0.0
260        for i in range(len(x)):
[c1c14ba]261            y_true[i] = self.pr_theory(x[i], radius)
[f3d51f6]262            sum_true += y_true[i]
[c1c14ba]263
264        y_true = y_true / sum_true * x_range / len(x)
265
[f3d51f6]266        # Show the theory P(r)
[a07e72f]267        new_plot = Data1D(x, y_true)
268        new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
[f3d51f6]269        new_plot.name = "P_{true}(r)"
270        new_plot.xaxis("\\rm{r}", 'A')
[c1c14ba]271        new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}")
[a3149c5]272        new_plot.id = "P_{true}(r)"
[e88ebfd]273        new_plot.group_id = "P_{true}(r)"
[db4ed82]274        self.parent.update_theory(data_id=self.data_id, theory=new_plot)
[c1c14ba]275        #Put this call in plottables/guitools
[db4ed82]276        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
[c1c14ba]277                                               title="Sphere P(r)"))
278
[b659551]279    def get_npts(self):
280        """
[7116b6e0]281        Returns the number of points in the I(q) data
[b659551]282        """
283        try:
284            return len(self.pr.x)
285        except:
286            return 0
[c1c14ba]287
[f3d51f6]288    def show_iq(self, out, pr, q=None):
[7116b6e0]289        """
[c1c14ba]290            Display computed I(q)
291        """
[f3d51f6]292        qtemp = pr.x
[4b73c3e]293        if not q == None:
[f3d51f6]294            qtemp = q
295
296        # Make a plot
297        maxq = -1
298        for q_i in qtemp:
[c1c14ba]299            if q_i > maxq:
300                maxq = q_i
301
[634f1cf]302        minq = 0.001
[c1c14ba]303
[634f1cf]304        # Check for user min/max
[4b73c3e]305        if not pr.q_min == None:
[634f1cf]306            minq = pr.q_min
[4b73c3e]307        if not pr.q_max == None:
[634f1cf]308            maxq = pr.q_max
[c1c14ba]309
310        x = pylab.arange(minq, maxq, maxq / 301.0)
[9a5097c]311        y = np.zeros(len(x))
312        err = np.zeros(len(x))
[f3d51f6]313        for i in range(len(x)):
314            value = pr.iq(out, x[i])
315            y[i] = value
316            try:
317                err[i] = math.sqrt(math.fabs(value))
318            except:
319                err[i] = 1.0
320                print "Error getting error", value, x[i]
[c1c14ba]321
[a07e72f]322        new_plot = Data1D(x, y)
323        new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
[2a92852]324        new_plot.name = IQ_FIT_LABEL
[f3d51f6]325        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
[c1c14ba]326        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
[d2ee6f6]327        title = "I(q)"
[a07e72f]328        new_plot.title = title
[c1c14ba]329
[d2ee6f6]330        # If we have a group ID, use it
331        if pr.info.has_key("plot_group_id"):
[e88ebfd]332            new_plot.group_id = pr.info["plot_group_id"]
[a07e72f]333        new_plot.id = IQ_FIT_LABEL
[db4ed82]334        self.parent.update_theory(data_id=self.data_id, theory=new_plot)
[c1c14ba]335
[d2ee6f6]336        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
[c1c14ba]337
[2a92852]338        # If we have used slit smearing, plot the smeared I(q) too
[4b73c3e]339        if pr.slit_width > 0 or pr.slit_height > 0:
[c1c14ba]340            x = pylab.arange(minq, maxq, maxq / 301.0)
[9a5097c]341            y = np.zeros(len(x))
342            err = np.zeros(len(x))
[2a92852]343            for i in range(len(x)):
344                value = pr.iq_smeared(out, x[i])
345                y[i] = value
346                try:
347                    err[i] = math.sqrt(math.fabs(value))
348                except:
349                    err[i] = 1.0
350                    print "Error getting error", value, x[i]
[c1c14ba]351
[a07e72f]352            new_plot = Data1D(x, y)
353            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
[2a92852]354            new_plot.name = IQ_SMEARED_LABEL
355            new_plot.xaxis("\\rm{Q}", 'A^{-1}')
[c1c14ba]356            new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
[f0ff8d65]357            # If we have a group ID, use it
358            if pr.info.has_key("plot_group_id"):
[c1c14ba]359                new_plot.group_id = pr.info["plot_group_id"]
[a07e72f]360            new_plot.id = IQ_SMEARED_LABEL
361            new_plot.title = title
[db4ed82]362            self.parent.update_theory(data_id=self.data_id, theory=new_plot)
[f0ff8d65]363            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
[c1c14ba]364
[b659551]365    def _on_pr_npts(self, evt):
366        """
[7116b6e0]367        Redisplay P(r) with a different number of points
[c1c14ba]368        """
[b659551]369        from inversion_panel import PrDistDialog
370        dialog = PrDistDialog(None, -1)
371        dialog.set_content(self._pr_npts)
372        if dialog.ShowModal() == wx.ID_OK:
[c1c14ba]373            self._pr_npts = dialog.get_content()
[b659551]374            dialog.Destroy()
[feb21d8]375            self.show_pr(self._last_out, self._last_pr, self._last_cov)
[b659551]376        else:
377            dialog.Destroy()
[c1c14ba]378
379
[f3d51f6]380    def show_pr(self, out, pr, cov=None):
[7116b6e0]381        """
[c1c14ba]382        """
[f3d51f6]383        # Show P(r)
[c1c14ba]384        x = pylab.arange(0.0, pr.d_max, pr.d_max / self._pr_npts)
385
[9a5097c]386        y = np.zeros(len(x))
387        dy = np.zeros(len(x))
388        y_true = np.zeros(len(x))
[f3d51f6]389
[c1c14ba]390        total = 0.0
[feb21d8]391        pmax = 0.0
[9a5097c]392        cov2 = np.ascontiguousarray(cov)
[c1c14ba]393
[f3d51f6]394        for i in range(len(x)):
[c1c14ba]395            if cov2 == None:
[f3d51f6]396                value = pr.pr(out, x[i])
397            else:
[dfb58f8]398                (value, dy[i]) = pr.pr_err(out, cov2, x[i])
[c1c14ba]399            total += value * pr.d_max / len(x)
400
[feb21d8]401            # keep track of the maximum P(r) value
[c1c14ba]402            if value > pmax:
[feb21d8]403                pmax = value
[c1c14ba]404
[feb21d8]405            y[i] = value
[c1c14ba]406
407        if self._normalize_output == True:
408            y = y / total
409            dy = dy / total
410        elif self._scale_output_unity == True:
411            y = y / pmax
412            dy = dy / pmax
413
414        if cov2 == None:
[a07e72f]415            new_plot = Data1D(x, y)
416            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
[f3d51f6]417        else:
[a07e72f]418            new_plot = Data1D(x, y, dy=dy)
[f1181977]419        new_plot.name = PR_FIT_LABEL
[f3d51f6]420        new_plot.xaxis("\\rm{r}", 'A')
[c1c14ba]421        new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}")
[75df58b]422        new_plot.title = "P(r) fit"
[a07e72f]423        new_plot.id = PR_FIT_LABEL
[d2ee6f6]424        # Make sure that the plot is linear
[75fbd17]425        new_plot.xtransform = "x"
[c1c14ba]426        new_plot.ytransform = "y"
[119dc89]427        new_plot.group_id = GROUP_ID_PR_FIT
[c1c14ba]428        self.parent.update_theory(data_id=self.data_id, theory=new_plot)
[f3d51f6]429        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
430        return x, pr.d_max
[c1c14ba]431
[75df58b]432    def load(self, data):
[0bae207]433        """
[7116b6e0]434        Load data. This will eventually be replaced
435        by our standard DataLoader class.
[0bae207]436        """
[c1c14ba]437        class FileData(object):
[0bae207]438            x = None
439            y = None
440            err = None
441            path = None
[c1c14ba]442
[0bae207]443            def __init__(self, path):
444                self.path = path
[c1c14ba]445
[75df58b]446        self._current_file_data = FileData(data.path)
[c1c14ba]447
[aab8ad7]448        # Use data loader to load file
[75df58b]449        dataread = data
[ceaf16e]450        # Notify the user if we could not read the file
451        if dataread is None:
452            raise RuntimeError, "Invalid data"
[c1c14ba]453
[aab8ad7]454        x = None
455        y = None
456        err = None
457        if dataread.__class__.__name__ == 'Data1D':
458            x = dataread.x
459            y = dataread.y
460            err = dataread.dy
461        else:
[c1c14ba]462            if isinstance(dataread, list) and len(dataread) > 0:
[e43c012]463                x = dataread[0].x
464                y = dataread[0].y
465                err = dataread[0].dy
466                msg = "PrView only allows a single data set at a time. "
[c1c14ba]467                msg += "Only the first data set was loaded."
[e43c012]468                wx.PostEvent(self.parent, StatusEvent(status=msg))
469            else:
[6f1f129]470                if dataread is None:
471                    return x, y, err
[e43c012]472                raise RuntimeError, "This tool can only read 1D data"
[c1c14ba]473
[0bae207]474        self._current_file_data.x = x
475        self._current_file_data.y = y
476        self._current_file_data.err = err
477        return x, y, err
[c1c14ba]478
479    def load_columns(self, path="sphere_60_q0_2.txt"):
[0bae207]480        """
[7116b6e0]481        Load 2- or 3- column ascii
[0bae207]482        """
[f3d51f6]483        # Read the data from the data file
[9a5097c]484        data_x = np.zeros(0)
485        data_y = np.zeros(0)
486        data_err = np.zeros(0)
[c1c14ba]487        scale = None
488        min_err = 0.0
[f3d51f6]489        if not path == None:
[c1c14ba]490            input_f = open(path, 'r')
491            buff = input_f.read()
492            lines = buff.split('\n')
[f3d51f6]493            for line in lines:
494                try:
495                    toks = line.split()
496                    x = float(toks[0])
497                    y = float(toks[1])
[c1c14ba]498                    if len(toks) > 2:
[b659551]499                        err = float(toks[2])
500                    else:
[c1c14ba]501                        if scale == None:
502                            scale = 0.05 * math.sqrt(y)
[b659551]503                            #scale = 0.05/math.sqrt(y)
[c1c14ba]504                            min_err = 0.01 * y
505                        err = scale * math.sqrt(y) + min_err
[b659551]506                        #err = 0
[c1c14ba]507
[9a5097c]508                    data_x = np.append(data_x, x)
509                    data_y = np.append(data_y, y)
510                    data_err = np.append(data_err, err)
[f3d51f6]511                except:
[c155a16]512                    logger.error(sys.exc_value)
[c1c14ba]513
514        if not scale == None:
[357b79b]515            message = "The loaded file had no error bars, statistical errors are assumed."
516            wx.PostEvent(self.parent, StatusEvent(status=message))
517        else:
518            wx.PostEvent(self.parent, StatusEvent(status=''))
[c1c14ba]519
520        return data_x, data_y, data_err
521
[0bae207]522    def load_abs(self, path):
523        """
[7116b6e0]524        Load an IGOR .ABS reduced file
[c1c14ba]525
[7116b6e0]526        :param path: file path
[c1c14ba]527
[7116b6e0]528        :return: x, y, err vectors
[c1c14ba]529
[0bae207]530        """
531        # Read the data from the data file
[9a5097c]532        data_x = np.zeros(0)
533        data_y = np.zeros(0)
534        data_err = np.zeros(0)
[c1c14ba]535        scale = None
536        min_err = 0.0
537
[0bae207]538        data_started = False
539        if not path == None:
[c1c14ba]540            input_f = open(path, 'r')
541            buff = input_f.read()
542            lines = buff.split('\n')
[0bae207]543            for line in lines:
[4b73c3e]544                if data_started == True:
[0bae207]545                    try:
546                        toks = line.split()
547                        x = float(toks[0])
548                        y = float(toks[1])
[c1c14ba]549                        if len(toks) > 2:
[0bae207]550                            err = float(toks[2])
551                        else:
[4b73c3e]552                            if scale == None:
[c1c14ba]553                                scale = 0.05 * math.sqrt(y)
[0bae207]554                                #scale = 0.05/math.sqrt(y)
[c1c14ba]555                                min_err = 0.01 * y
556                            err = scale * math.sqrt(y) + min_err
[0bae207]557                            #err = 0
[c1c14ba]558
[9a5097c]559                        data_x = np.append(data_x, x)
560                        data_y = np.append(data_y, y)
561                        data_err = np.append(data_err, err)
[0bae207]562                    except:
[c155a16]563                        logger.error(sys.exc_value)
[c1c14ba]564                elif line.find("The 6 columns") >= 0:
565                    data_started = True
566
[4b73c3e]567        if not scale == None:
[0bae207]568            message = "The loaded file had no error bars, statistical errors are assumed."
569            wx.PostEvent(self.parent, StatusEvent(status=message))
570        else:
571            wx.PostEvent(self.parent, StatusEvent(status=''))
[c1c14ba]572
573        return data_x, data_y, data_err
574
[f3d51f6]575    def pr_theory(self, r, R):
[c1c14ba]576        """
577            Return P(r) of a sphere for a given R
[4b73c3e]578            For test purposes
[f3d51f6]579        """
[c1c14ba]580        if r <= 2 * R:
581            return 12.0 * ((0.5 * r / R) ** 2) * ((1.0 - 0.5 * r / R) ** 2) * (2.0 + 0.5 * r / R)
[f3d51f6]582        else:
583            return 0.0
584
[a07e72f]585    def get_context_menu(self, plotpanel=None):
[f3d51f6]586        """
[7116b6e0]587        Get the context menu items available for P(r)
[c1c14ba]588
[7116b6e0]589        :param graph: the Graph object to which we attach the context menu
[c1c14ba]590
[7116b6e0]591        :return: a list of menu items with call-back function
[c1c14ba]592
[f3d51f6]593        """
[a07e72f]594        graph = plotpanel.graph
[660b1e6]595        # Look whether this Graph contains P(r) data
[a07e72f]596        if graph.selected_plottable not in plotpanel.plots:
597            return []
598        item = plotpanel.plots[graph.selected_plottable]
599        if item.id == PR_FIT_LABEL:
600            #add_data_hint = "Load a data file and display it on this plot"
601            #["Add P(r) data",add_data_hint , self._on_add_data],
602            change_n_hint = "Change the number of"
603            change_n_hint += " points on the P(r) output"
604            change_n_label = "Change number of P(r) points"
[c1c14ba]605            m_list = [[change_n_label, change_n_hint, self._on_pr_npts]]
[feb21d8]606
[a07e72f]607            if self._scale_output_unity or self._normalize_output:
608                hint = "Let the output P(r) keep the scale of the data"
[c1c14ba]609                m_list.append(["Disable P(r) scaling", hint,
[a07e72f]610                               self._on_disable_scaling])
611            if not self._scale_output_unity:
[c1c14ba]612                m_list.append(["Scale P_max(r) to unity",
613                               "Scale P(r) so that its maximum is 1",
[a07e72f]614                               self._on_scale_unity])
615            if not self._normalize_output:
[c1c14ba]616                m_list.append(["Normalize P(r) to unity",
617                               "Normalize the integral of P(r) to 1",
[a07e72f]618                               self._on_normalize])
[c1c14ba]619
[a07e72f]620            return m_list
[c1c14ba]621
622        elif item.id in [PR_LOADED_LABEL, IQ_DATA_LABEL, IQ_FIT_LABEL, IQ_SMEARED_LABEL]:
[a07e72f]623            return []
624        elif item.id == graph.selected_plottable:
[f21d496]625            if issubclass(item.__class__, Data1D):
[c1c14ba]626                return [["Compute P(r)",
627                         "Compute P(r) from distribution",
628                         self._on_context_inversion]]
[aa4b8379]629        return []
[660b1e6]630
[feb21d8]631    def _on_disable_scaling(self, evt):
632        """
[7116b6e0]633        Disable P(r) scaling
[c1c14ba]634
[7116b6e0]635        :param evt: Menu event
[c1c14ba]636
[feb21d8]637        """
638        self._normalize_output = False
639        self._scale_output_unity = False
640        self.show_pr(self._last_out, self._last_pr, self._last_cov)
[c1c14ba]641
[f1181977]642        # Now replot the original added data
643        for plot in self._added_plots:
[9a5097c]644            self._added_plots[plot].y = np.copy(self._default_Iq[plot])
[c1c14ba]645            wx.PostEvent(self.parent,
646                         NewPlotEvent(plot=self._added_plots[plot],
[75df58b]647                                      title=self._added_plots[plot].name,
[c1c14ba]648                                      update=True))
649
[f1181977]650        # Need the update flag in the NewPlotEvent to protect against
651        # the plot no longer being there...
[c1c14ba]652
[feb21d8]653    def _on_normalize(self, evt):
654        """
[7116b6e0]655        Normalize the area under the P(r) curve to 1.
656        This operation is done for all displayed plots.
[c1c14ba]657
[7116b6e0]658        :param evt: Menu event
[c1c14ba]659
[feb21d8]660        """
661        self._normalize_output = True
662        self._scale_output_unity = False
[c1c14ba]663
[feb21d8]664        self.show_pr(self._last_out, self._last_pr, self._last_cov)
[c1c14ba]665
[f1181977]666        # Now scale the added plots too
667        for plot in self._added_plots:
[9a5097c]668            total = np.sum(self._added_plots[plot].y)
[f1181977]669            npts = len(self._added_plots[plot].x)
[c1c14ba]670            total *= self._added_plots[plot].x[npts - 1] / npts
671            y = self._added_plots[plot].y / total
672
[a07e72f]673            new_plot = Data1D(self._added_plots[plot].x, y)
674            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
[e88ebfd]675            new_plot.group_id = self._added_plots[plot].group_id
[a07e72f]676            new_plot.id = self._added_plots[plot].id
677            new_plot.title = self._added_plots[plot].title
[f1181977]678            new_plot.name = self._added_plots[plot].name
679            new_plot.xaxis("\\rm{r}", 'A')
[c1c14ba]680            new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}")
681            self.parent.update_theory(data_id=self.data_id, theory=new_plot)
682            wx.PostEvent(self.parent,
[75df58b]683                         NewPlotEvent(plot=new_plot, update=True,
[c1c14ba]684                                      title=self._added_plots[plot].name))
685
[feb21d8]686    def _on_scale_unity(self, evt):
687        """
[7116b6e0]688        Scale the maximum P(r) value on each displayed plot to 1.
[c1c14ba]689
[7116b6e0]690        :param evt: Menu event
[c1c14ba]691
[feb21d8]692        """
693        self._scale_output_unity = True
694        self._normalize_output = False
[c1c14ba]695
[feb21d8]696        self.show_pr(self._last_out, self._last_pr, self._last_cov)
[c1c14ba]697
[f1181977]698        # Now scale the added plots too
699        for plot in self._added_plots:
700            _max = 0
701            for y in self._added_plots[plot].y:
[c1c14ba]702                if y > _max:
[f1181977]703                    _max = y
[c1c14ba]704            y = self._added_plots[plot].y / _max
705
[a07e72f]706            new_plot = Data1D(self._added_plots[plot].x, y)
707            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
[f1181977]708            new_plot.name = self._added_plots[plot].name
709            new_plot.xaxis("\\rm{r}", 'A')
[c1c14ba]710            new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}")
711            self.parent.update_theory(data_id=self.data_id, theory=new_plot)
712            wx.PostEvent(self.parent,
[75df58b]713                         NewPlotEvent(plot=new_plot, update=True,
[c1c14ba]714                                      title=self._added_plots[plot].name))
715
[f3d51f6]716    def start_thread(self):
[7116b6e0]717        """
[c1c14ba]718            Start a calculation thread
[7116b6e0]719        """
[f3d51f6]720        from pr_thread import CalcPr
[c1c14ba]721
[f3d51f6]722        # If a thread is already started, stop it
723        if self.calc_thread != None and self.calc_thread.isrunning():
724            self.calc_thread.stop()
[c1c14ba]725            ## stop just raises the flag -- the thread is supposed to
[f3efa09]726            ## then kill itself. In August 2014 it was shown that this is
[c1c14ba]727            ## incorrectly handled by fitting.py and a fix implemented.
[f3efa09]728            ## It is not clear whether it is properly used here, but the
729            ## "fix" of waiting for the previous thread to end breaks the
730            ## pr perspective completely as it causes an infinite loop.
731            ## Thus it is likely the threading is bing properly handled.
732            ## While the "fix" is no longer implemented the comment is
733            ## left here till somebody ascertains that in fact the threads
734            ## are being properly handled.
[f4167637]735            ##
[c1c14ba]736            ##    -PDB January 25, 2015
737
[f3d51f6]738        pr = self.pr.clone()
[75df58b]739        self.calc_thread = CalcPr(pr, self.nfunc,
[c1c14ba]740                                  error_func=self._thread_error,
741                                  completefn=self._completed, updatefn=None)
[f3d51f6]742        self.calc_thread.queue()
743        self.calc_thread.ready(2.5)
[c1c14ba]744
[f3d51f6]745    def _thread_error(self, error):
[7116b6e0]746        """
[c1c14ba]747            Call-back method for calculation errors
[7116b6e0]748        """
[f3d51f6]749        wx.PostEvent(self.parent, StatusEvent(status=error))
[c1c14ba]750
[32dffae4]751    def _estimate_completed(self, alpha, message, elapsed):
[f3d51f6]752        """
[c1c14ba]753        Parameter estimation completed,
[7116b6e0]754        display the results to the user
[c1c14ba]755
[7116b6e0]756        :param alpha: estimated best alpha
757        :param elapsed: computation time
[c1c14ba]758
[f3d51f6]759        """
760        # Save useful info
761        self.elapsed = elapsed
762        self.control_panel.alpha_estimate = alpha
[c1c14ba]763        if not message == None:
[32dffae4]764            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
[35adaf6]765        self.perform_estimateNT()
[c1c14ba]766
[35adaf6]767    def _estimateNT_completed(self, nterms, alpha, message, elapsed):
768        """
[c1c14ba]769        Parameter estimation completed,
[7116b6e0]770        display the results to the user
[c1c14ba]771
[7116b6e0]772        :param alpha: estimated best alpha
773        :param nterms: estimated number of terms
774        :param elapsed: computation time
[c1c14ba]775
[35adaf6]776        """
777        # Save useful info
778        self.elapsed = elapsed
779        self.control_panel.nterms_estimate = nterms
780        self.control_panel.alpha_estimate = alpha
[c1c14ba]781        if not message == None:
[35adaf6]782            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
[c1c14ba]783
[380ff18]784    def _completed(self, out, cov, pr, elapsed):
[82d0e2c]785        """
786        wxCallAfter Method called with the results when the inversion
787        is done
[c1c14ba]788
[82d0e2c]789        :param out: output coefficient for the base functions
790        :param cov: covariance matrix
791        :param pr: Invertor instance
792        :param elapsed: time spent computing
793        """
[376ee3d]794        # Ensure hat you have all inputs are ready at the time call happens:
795        # Without CallAfter, it will freeze with wx >= 2.9.
[83267f9]796        wx.CallAfter(self._completed_call, out, cov, pr, elapsed)
[c1c14ba]797
[380ff18]798    def _completed_call(self, out, cov, pr, elapsed):
[f3d51f6]799        """
[7116b6e0]800        Method called with the results when the inversion
801        is done
[c1c14ba]802
[7116b6e0]803        :param out: output coefficient for the base functions
804        :param cov: covariance matrix
805        :param pr: Invertor instance
806        :param elapsed: time spent computing
[c1c14ba]807
[f3d51f6]808        """
809        # Save useful info
810        self.elapsed = elapsed
[feb21d8]811        # Keep a copy of the last result
[c1c14ba]812        self._last_pr = pr.clone()
[feb21d8]813        self._last_out = out
814        self._last_cov = cov
[c1c14ba]815
[b659551]816        # Save Pr invertor
817        self.pr = pr
[9a5097c]818        cov = np.ascontiguousarray(cov)
[dfb58f8]819
[f3d51f6]820        # Show result on control panel
821        self.control_panel.chi2 = pr.chi2
822        self.control_panel.elapsed = elapsed
823        self.control_panel.oscillation = pr.oscillations(out)
[dfb58f8]824        self.control_panel.positive = pr.get_positive(out)
[c1c14ba]825        self.control_panel.pos_err = pr.get_pos_err(out, cov)
[a4bd2ac]826        self.control_panel.rg = pr.rg(out)
827        self.control_panel.iq0 = pr.iq0(out)
828        self.control_panel.bck = pr.background
[c1c14ba]829
[f3d51f6]830        # Show I(q) fit
[59afa71]831        self.show_iq(out, self.pr)
[c1c14ba]832
[f3d51f6]833        # Show P(r) fit
[c1c14ba]834        self.show_pr(out, self.pr, cov)
835
[75df58b]836    def show_data(self, path=None, data=None, reset=False):
[2a92852]837        """
[7116b6e0]838        Show data read from a file
[c1c14ba]839
[7116b6e0]840        :param path: file path
841        :param reset: if True all other plottables will be cleared
[c1c14ba]842
[2a92852]843        """
[75df58b]844        #if path is not None:
845        if data is not None:
[ea5551f]846            try:
[75df58b]847                pr = self._create_file_pr(data)
[ceaf16e]848            except:
[75df58b]849                status = "Problem reading data: %s" % sys.exc_value
[ceaf16e]850                wx.PostEvent(self.parent, StatusEvent(status=status))
851                raise RuntimeError, status
[c1c14ba]852
[ceaf16e]853            # If the file contains nothing, just return
854            if pr is None:
855                raise RuntimeError, "Loaded data is invalid"
[c1c14ba]856
[ceaf16e]857            self.pr = pr
[c1c14ba]858
[660b1e6]859        # Make a plot of I(q) data
[a07e72f]860        if self.pr.err == None:
861            new_plot = Data1D(self.pr.x, self.pr.y)
862            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
[357b79b]863        else:
864            new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
[f1181977]865        new_plot.name = IQ_DATA_LABEL
[660b1e6]866        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
[c1c14ba]867        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
[aa4b8379]868        new_plot.interactive = True
[119dc89]869        new_plot.group_id = GROUP_ID_IQ_DATA
[db4ed82]870        new_plot.id = self.data_id
[c1c14ba]871        new_plot.title = "I(q)"
872        wx.PostEvent(self.parent,
[75df58b]873                     NewPlotEvent(plot=new_plot, title="I(q)", reset=reset))
[c1c14ba]874
[0d88a09]875        self.current_plottable = new_plot
[aa4b8379]876        # Get Q range
[0320d36]877        self.control_panel.q_min = min(self.pr.x)
878        self.control_panel.q_max = max(self.pr.x)
[c1c14ba]879
[410aad8]880    def save_data(self, filepath, prstate=None):
881        """
[7116b6e0]882        Save data in provided state object.
[c1c14ba]883
884        :TODO: move the state code away from inversion_panel and move it here.
[7116b6e0]885                Then remove the "prstate" input and make this method private.
[c1c14ba]886
[7116b6e0]887        :param filepath: path of file to write to
[c1c14ba]888        :param prstate: P(r) inversion state
889
[410aad8]890        """
891        #TODO: do we need this or can we use DataLoader.loader.save directly?
[c1c14ba]892
[410aad8]893        # Add output data and coefficients to state
894        prstate.coefficients = self._last_out
895        prstate.covariance = self._last_cov
[c1c14ba]896
[410aad8]897        # Write the output to file
[0ab485b]898        # First, check that the data is of the right type
[75df58b]899        if issubclass(self.current_plottable.__class__,
[c10d9d6c]900                      sas.sascalc.dataloader.data_info.Data1D):
[0ab485b]901            self.state_reader.write(filepath, self.current_plottable, prstate)
902        else:
[75df58b]903            msg = "pr.save_data: the data being saved is not a"
[c1c14ba]904            msg += " sas.data_info.Data1D object"
[75df58b]905            raise RuntimeError, msg
[c1c14ba]906
907    def setup_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None,
[2a92852]908                             bck=False, height=0, width=0):
[7116b6e0]909        """
[c1c14ba]910            Set up inversion from plotted data
[7116b6e0]911        """
[f3d51f6]912        self.alpha = alpha
913        self.nfunc = nfunc
914        self.max_length = d_max
[634f1cf]915        self.q_min = q_min
916        self.q_max = q_max
[a4bd2ac]917        self.has_bck = bck
[2a92852]918        self.slit_height = height
[c1c14ba]919        self.slit_width = width
920
[4a5de6f]921        try:
[2a92852]922            pr = self._create_plot_pr()
[c1c14ba]923            if not pr == None:
[2a92852]924                self.pr = pr
925                self.perform_inversion()
[4a5de6f]926        except:
927            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
[f3d51f6]928
[c1c14ba]929    def estimate_plot_inversion(self, alpha, nfunc, d_max,
930                                q_min=None, q_max=None,
[2a92852]931                                bck=False, height=0, width=0):
[7116b6e0]932        """
[c1c14ba]933            Estimate parameters from plotted data
[7116b6e0]934        """
[f3d51f6]935        self.alpha = alpha
936        self.nfunc = nfunc
937        self.max_length = d_max
[634f1cf]938        self.q_min = q_min
939        self.q_max = q_max
[a4bd2ac]940        self.has_bck = bck
[2a92852]941        self.slit_height = height
[c1c14ba]942        self.slit_width = width
943
[4a5de6f]944        try:
[2a92852]945            pr = self._create_plot_pr()
[c1c14ba]946            if not pr == None:
[2a92852]947                self.pr = pr
[4ccbc67]948                self.perform_estimate()
[4a5de6f]949        except:
[c1c14ba]950            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
[f3d51f6]951
[2a92852]952    def _create_plot_pr(self, estimate=False):
[f3d51f6]953        """
[7116b6e0]954        Create and prepare invertor instance from
955        a plottable data set.
[c1c14ba]956
957        :param path: path of the file to read in
958
[f3d51f6]959        """
[ceaf16e]960        # Sanity check
961        if self.current_plottable is None:
962            msg = "Please load a valid data set before proceeding."
[c1c14ba]963            wx.PostEvent(self.parent, StatusEvent(status=msg))
964            return None
965
[f3d51f6]966        # Get the data from the chosen data set and perform inversion
967        pr = Invertor()
968        pr.d_max = self.max_length
969        pr.alpha = self.alpha
[634f1cf]970        pr.q_min = self.q_min
971        pr.q_max = self.q_max
[f3d51f6]972        pr.x = self.current_plottable.x
973        pr.y = self.current_plottable.y
[a4bd2ac]974        pr.has_bck = self.has_bck
[2a92852]975        pr.slit_height = self.slit_height
976        pr.slit_width = self.slit_width
[c1c14ba]977
[d2ee6f6]978        # Keep track of the plot window title to ensure that
979        # we can overlay the plots
[e88ebfd]980        pr.info["plot_group_id"] = self.current_plottable.group_id
[c1c14ba]981
[f3d51f6]982        # Fill in errors if none were provided
[d2ee6f6]983        err = self.current_plottable.dy
984        all_zeros = True
985        if err == None:
[9a5097c]986            err = np.zeros(len(pr.y))
[c1c14ba]987        else:
[d2ee6f6]988            for i in range(len(err)):
[c1c14ba]989                if err[i] > 0:
[d2ee6f6]990                    all_zeros = False
[c1c14ba]991
992        if all_zeros:
[d2ee6f6]993            scale = None
994            min_err = 0.0
[f3d51f6]995            for i in range(len(pr.y)):
[d2ee6f6]996                # Scale the error so that we can fit over several decades of Q
[c1c14ba]997                if scale == None:
998                    scale = 0.05 * math.sqrt(pr.y[i])
999                    min_err = 0.01 * pr.y[i]
1000                err[i] = scale * math.sqrt(math.fabs(pr.y[i])) + min_err
[75df58b]1001            message = "The loaded file had no error bars, "
1002            message += "statistical errors are assumed."
[d2ee6f6]1003            wx.PostEvent(self.parent, StatusEvent(status=message))
1004
1005        pr.err = err
[c1c14ba]1006
[2a92852]1007        return pr
[f3d51f6]1008
[75df58b]1009    def setup_file_inversion(self, alpha, nfunc, d_max, data,
[c1c14ba]1010                             path=None, q_min=None, q_max=None,
[2a92852]1011                             bck=False, height=0, width=0):
[7116b6e0]1012        """
[c1c14ba]1013            Set up inversion
[7116b6e0]1014        """
[f3d51f6]1015        self.alpha = alpha
1016        self.nfunc = nfunc
1017        self.max_length = d_max
[634f1cf]1018        self.q_min = q_min
1019        self.q_max = q_max
[a4bd2ac]1020        self.has_bck = bck
[2a92852]1021        self.slit_height = height
[c1c14ba]1022        self.slit_width = width
1023
[4a5de6f]1024        try:
[75df58b]1025            pr = self._create_file_pr(data)
[c1c14ba]1026            if not pr == None:
[2a92852]1027                self.pr = pr
[4a5de6f]1028                self.perform_inversion()
1029        except:
1030            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
[c1c14ba]1031
[75df58b]1032    def estimate_file_inversion(self, alpha, nfunc, d_max, data,
[c1c14ba]1033                                path=None, q_min=None, q_max=None,
[2a92852]1034                                bck=False, height=0, width=0):
[7116b6e0]1035        """
[c1c14ba]1036            Estimate parameters for inversion
[7116b6e0]1037        """
[f3d51f6]1038        self.alpha = alpha
1039        self.nfunc = nfunc
1040        self.max_length = d_max
[634f1cf]1041        self.q_min = q_min
1042        self.q_max = q_max
[a4bd2ac]1043        self.has_bck = bck
[2a92852]1044        self.slit_height = height
[c1c14ba]1045        self.slit_width = width
1046
[4a5de6f]1047        try:
[75df58b]1048            pr = self._create_file_pr(data)
[c1d5aea]1049            if pr is not None:
[2a92852]1050                self.pr = pr
[4ccbc67]1051                self.perform_estimate()
[4a5de6f]1052        except:
1053            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
[c1c14ba]1054
[75df58b]1055    def _create_file_pr(self, data):
[f3d51f6]1056        """
[7116b6e0]1057        Create and prepare invertor instance from
1058        a file data set.
[c1c14ba]1059
1060        :param path: path of the file to read in
1061
[75df58b]1062        """
1063        # Reset the status bar so that we don't get mixed up
[c1c14ba]1064        # with old messages.
[75df58b]1065        #TODO: refactor this into a proper status handling
1066        wx.PostEvent(self.parent, StatusEvent(status=''))
1067        try:
[c1c14ba]1068            class FileData(object):
[75df58b]1069                x = None
1070                y = None
1071                err = None
1072                path = None
1073                def __init__(self, path):
1074                    self.path = path
[c1c14ba]1075
[75df58b]1076            self._current_file_data = FileData(data.path)
1077            self._current_file_data.x = data.x
1078            self._current_file_data.y = data.y
1079            self._current_file_data.err = data.dy
1080            x, y, err = data.x, data.y, data.dy
1081        except:
1082            load_error(sys.exc_value)
1083            return None
[c1c14ba]1084
[75df58b]1085        # If the file contains no data, just return
1086        if x is None or len(x) == 0:
1087            load_error("The loaded file contains no data")
1088            return None
[59d542c]1089
[75df58b]1090        # If we have not errors, add statistical errors
[59d542c]1091        if y is not None:
[9a5097c]1092            if err == None or np.all(err) == 0:
1093                err = np.zeros(len(y))
[59d542c]1094                scale = None
1095                min_err = 0.0
1096                for i in range(len(y)):
1097                    # Scale the error so that we can fit over several decades of Q
1098                    if scale == None:
1099                        scale = 0.05 * math.sqrt(y[i])
1100                        min_err = 0.01 * y[i]
1101                    err[i] = scale * math.sqrt(math.fabs(y[i])) + min_err
1102                message = "The loaded file had no error bars, "
1103                message += "statistical errors are assumed."
1104                wx.PostEvent(self.parent, StatusEvent(status=message))
[c1c14ba]1105
[75df58b]1106        try:
1107            # Get the data from the chosen data set and perform inversion
1108            pr = Invertor()
1109            pr.d_max = self.max_length
1110            pr.alpha = self.alpha
1111            pr.q_min = self.q_min
1112            pr.q_max = self.q_max
1113            pr.x = x
1114            pr.y = y
1115            pr.err = err
1116            pr.has_bck = self.has_bck
1117            pr.slit_height = self.slit_height
1118            pr.slit_width = self.slit_width
1119            return pr
1120        except:
1121            load_error(sys.exc_value)
[2a92852]1122        return None
[c1c14ba]1123
[f3d51f6]1124    def perform_estimate(self):
[7116b6e0]1125        """
[c1c14ba]1126            Perform parameter estimation
[7116b6e0]1127        """
[f3d51f6]1128        from pr_thread import EstimatePr
[c1c14ba]1129
[f3d51f6]1130        # If a thread is already started, stop it
[75df58b]1131        if self.estimation_thread != None and \
1132            self.estimation_thread.isrunning():
[f3d51f6]1133            self.estimation_thread.stop()
[c1c14ba]1134            ## stop just raises the flag -- the thread is supposed to
[f3efa09]1135            ## then kill itself. In August 2014 it was shown that this is
[c1c14ba]1136            ## incorrectly handled by fitting.py and a fix implemented.
[f3efa09]1137            ## It is not clear whether it is properly used here, but the
1138            ## "fix" of waiting for the previous thread to end breaks the
1139            ## pr perspective completely as it causes an infinite loop.
1140            ## Thus it is likely the threading is bing properly handled.
1141            ## While the "fix" is no longer implemented the comment is
1142            ## left here till somebody ascertains that in fact the threads
1143            ## are being properly handled.
[f4167637]1144            ##
[c1c14ba]1145            ##    -PDB January 25, 2015
[f3d51f6]1146        pr = self.pr.clone()
[75df58b]1147        self.estimation_thread = EstimatePr(pr, self.nfunc,
[c1c14ba]1148                                            error_func=self._thread_error,
1149                                            completefn=self._estimate_completed,
1150                                            updatefn=None)
[f3d51f6]1151        self.estimation_thread.queue()
1152        self.estimation_thread.ready(2.5)
[c1c14ba]1153
[35adaf6]1154    def perform_estimateNT(self):
[7116b6e0]1155        """
[c1c14ba]1156            Perform parameter estimation
[7116b6e0]1157        """
[35adaf6]1158        from pr_thread import EstimateNT
[c1c14ba]1159
[35adaf6]1160        # If a thread is already started, stop it
1161        if self.estimation_thread != None and self.estimation_thread.isrunning():
1162            self.estimation_thread.stop()
[c1c14ba]1163            ## stop just raises the flag -- the thread is supposed to
[f3efa09]1164            ## then kill itself. In August 2014 it was shown that this is
[c1c14ba]1165            ## incorrectly handled by fitting.py and a fix implemented.
[f3efa09]1166            ## It is not clear whether it is properly used here, but the
1167            ## "fix" of waiting for the previous thread to end breaks the
1168            ## pr perspective completely as it causes an infinite loop.
1169            ## Thus it is likely the threading is bing properly handled.
1170            ## While the "fix" is no longer implemented the comment is
1171            ## left here till somebody ascertains that in fact the threads
1172            ## are being properly handled.
[f4167637]1173            ##
[c1c14ba]1174            ##    -PDB January 25, 2015
[35adaf6]1175        pr = self.pr.clone()
[2a92852]1176        # Skip the slit settings for the estimation
1177        # It slows down the application and it doesn't change the estimates
1178        pr.slit_height = 0.0
[c1c14ba]1179        pr.slit_width = 0.0
1180        self.estimation_thread = EstimateNT(pr, self.nfunc,
1181                                            error_func=self._thread_error,
1182                                            completefn=self._estimateNT_completed,
1183                                            updatefn=None)
[35adaf6]1184        self.estimation_thread.queue()
1185        self.estimation_thread.ready(2.5)
[c1c14ba]1186
[f3d51f6]1187    def perform_inversion(self):
[7116b6e0]1188        """
[c1c14ba]1189            Perform inversion
[7116b6e0]1190        """
[f3d51f6]1191        self.start_thread()
[c1c14ba]1192
[f3d51f6]1193    def _on_context_inversion(self, event):
[75df58b]1194        """
[c1c14ba]1195            Call-back method for plot context menu
[75df58b]1196        """
[f3d51f6]1197        panel = event.GetEventObject()
[45f896f]1198        Plugin.on_perspective(self, event=event)
[f3d51f6]1199
1200        # If we have more than one displayed plot, make the user choose
[45f896f]1201        if len(panel.plots) >= 1 and \
[75df58b]1202            panel.graph.selected_plottable in panel.plots:
[119dc89]1203            dataset = panel.plots[panel.graph.selected_plottable].name
[f3d51f6]1204        else:
[c155a16]1205            logger.info("Prview Error: No data is available")
[f3d51f6]1206            return
[c1c14ba]1207
[f3d51f6]1208        # Store a reference to the current plottable
[3fd1ebc]1209        # If we have a suggested value, use it.
1210        try:
1211            estimate = float(self.control_panel.alpha_estimate)
1212            self.control_panel.alpha = estimate
1213        except:
1214            self.control_panel.alpha = self.alpha
[c155a16]1215            logger.info("Prview :Alpha Not estimate yet")
[d6113849]1216        try:
1217            estimate = int(self.control_panel.nterms_estimate)
1218            self.control_panel.nfunc = estimate
1219        except:
1220            self.control_panel.nfunc = self.nfunc
[c155a16]1221            logger.info("Prview : ntemrs Not estimate yet")
[c1c14ba]1222
[6512644]1223        self.current_plottable = panel.plots[panel.graph.selected_plottable]
[45f896f]1224        self.set_data([self.current_plottable])
[f3d51f6]1225        self.control_panel.plotname = dataset
[d6113849]1226        #self.control_panel.nfunc = self.nfunc
[f3d51f6]1227        self.control_panel.d_max = self.max_length
[45f896f]1228        #self.parent.set_perspective(self.perspective)
[aa4b8379]1229        self.control_panel._on_invert(None)
[c1c14ba]1230
[f3d51f6]1231    def get_panels(self, parent):
1232        """
1233            Create and return a list of panel objects
1234        """
1235        from inversion_panel import InversionControl
[c1c14ba]1236
[f3d51f6]1237        self.parent = parent
[c1c14ba]1238        self.frame = MDIFrame(self.parent, None, 'None', (100, 200))
1239        self.control_panel = InversionControl(self.frame, -1,
[f21d496]1240                                              style=wx.RAISED_BORDER)
[ae84427]1241        self.frame.set_panel(self.control_panel)
1242        self._frame_set_helper()
[f3d51f6]1243        self.control_panel.set_manager(self)
1244        self.control_panel.nfunc = self.nfunc
1245        self.control_panel.d_max = self.max_length
1246        self.control_panel.alpha = self.alpha
1247        self.perspective = []
1248        self.perspective.append(self.control_panel.window_name)
[c1c14ba]1249
[f3d51f6]1250        return [self.control_panel]
[c1c14ba]1251
[e88ebfd]1252    def set_data(self, data_list=None):
[aa4b8379]1253        """
[75df58b]1254        receive a list of data to compute pr
[aa4b8379]1255        """
[a3149c5]1256        if data_list is None:
1257            data_list = []
[e88ebfd]1258        if len(data_list) >= 1:
1259            if len(data_list) == 1:
1260                data = data_list[0]
[75df58b]1261            else:
[3ecaa2b]1262                data_1d_list = []
1263                data_2d_list = []
1264                error_msg = ""
1265                # separate data into data1d and data2d list
1266                for data in data_list:
1267                    if data is not None:
1268                        if issubclass(data.__class__, Data1D):
1269                            data_1d_list.append(data)
1270                        else:
[c1c14ba]1271                            error_msg += " %s type %s \n" % (str(data.name),
1272                                                             str(data.__class__.__name__))
[3ecaa2b]1273                            data_2d_list.append(data)
1274                if len(data_2d_list) > 0:
1275                    msg = "PrView does not support the following data types:\n"
1276                    msg += error_msg
1277                if len(data_1d_list) == 0:
[c1c14ba]1278                    wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
[3ecaa2b]1279                    return
[a69a967]1280                msg = "Prview does not allow multiple data!\n"
[3ecaa2b]1281                msg += "Please select one.\n"
[6813da7d]1282                if len(data_list) > 1:
1283                    from pr_widgets import DataDialog
1284                    dlg = DataDialog(data_list=data_1d_list, text=msg)
1285                    if dlg.ShowModal() == wx.ID_OK:
1286                        data = dlg.get_data()
1287                    else:
1288                        data = None
1289                    dlg.Destroy()
[e88ebfd]1290            if data is None:
[3ecaa2b]1291                msg += "PrView receives no data. \n"
[c1c14ba]1292                wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
[e88ebfd]1293                return
1294            if issubclass(data.__class__, Data1D):
1295                try:
[c1c14ba]1296                    wx.PostEvent(self.parent,
1297                                 NewPlotEvent(action='remove',
1298                                              group_id=GROUP_ID_IQ_DATA,
1299                                              id=self.data_id))
[db4ed82]1300                    self.data_id = data.id
[e88ebfd]1301                    self.control_panel._change_file(evt=None, data=data)
1302                except:
[4b73c3e]1303                    msg = "Prview Set_data: " + str(sys.exc_value)
[c1c14ba]1304                    wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
1305            else:
[e88ebfd]1306                msg = "Pr cannot be computed for data of "
1307                msg += "type %s" % (data_list[0].__class__.__name__)
[c1c14ba]1308                wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
[75a7ece]1309        else:
1310            msg = "Pr contain no data"
1311            wx.PostEvent(self.parent, StatusEvent(status=msg, info='warning'))
[c1c14ba]1312
[f3d51f6]1313    def post_init(self):
1314        """
1315            Post initialization call back to close the loose ends
1316            [Somehow openGL needs this call]
1317        """
[f21d496]1318        pass
Note: See TracBrowser for help on using the repository browser.