def fill(self, cpf):
        """
        Fill the histogram using a list of numbers and compare
        with the Poisson.
        @param [in] A list of the clusters per frame.
        """

        lg.info(" *")
        lg.info(" * Starting the fill routine for RateHistogram.")
        lg.info(" *")

        ## A list of the clusters per frame.
        self.__cpf = cpf

        ## The number of frames.
        self.__n_f = len(self.__cpf)

        ## The total number of clusters.
        self.__n_k = sum(self.__cpf)

        ## The estimated overall rate.
        self.__Lambda_est = float(self.__n_k) / float(self.__n_f)

        ## The standard error on the estimated overall rate.
        self.__Lambda_est_err = np.sqrt(float(self.__n_k)) / float(self.__n_f)

        lg.info(" * Number of frames supplied    (N)   = %d" % (self.__n_f))
        lg.info(" * The total number of clusters (M)   = %d" % (self.__n_k))
        lg.info(" *")
        lg.info(" * => \Lambda = (%f +- %f) [particles per unit time]" % (self.__Lambda_est, self.__Lambda_est_err))
        lg.info(" *")

        # Work out the bin details.
        self.max_x = max(self.__cpf)

        # Round this up to the nearest 10.
        self.max_x_r = np.floor(float(self.max_x)/10.) * 10. + 10.

        # Create the bin edges array for the data:
        # * Bin width one, rounded-up max x value.
        self.bins = np.arange(0, self.max_x_r + self.bin_w, self.bin_w)

        # Plot the histogram.

        ## A list of the histogram bin contents (n).
        self.__n = None

        ## A list of the histogram bins.
        self.__bins = None

        #self.n, self.bins, self.patches = plt.hist(self.cpf, bins=self.bins, histtype='stepfilled')
        self.__n, self.__bins = np.histogram(self.__cpf, bins=self.bins)

        lg.info(" * The histogram bin contents:")
        lg.info(self.__n)
        lg.info(" *--> Total number of frames (check): %d" % (sum(self.__n)))
        lg.info(" *")

        lg.info(" * The histogram bin values:")
        lg.info(self.__bins)
        lg.info(" *")

        # Set the colour of the histogram patches (make configurable?).
        #plt.setp(self.patches, 'facecolor', '#44BB11', 'alpha', 0.75, 'linewidth', 0.0)

        #print("* DEBUG: Number of hit pixels in each frame:", self.__cpf)
        #print("* DEBUG: Max number of clusters per frame  : %6d" % (self.max_x))
        #print("* DEBUG: x max                             : %6d" % (self.max_x_r))
        #print("* DEBUG: x bin width                       : %6d" % (self.bin_w))
        #print("* DEBUG: bins:", self.bins)
        #print("* DEBUG:")

        # Fit to Poisson?
        if self.__n[0] != self.__n_f: # If not completely empty...

            ## The Poisson function to fit to!
            fitfunc = lambda p, x: p[0]*pow(p[1],x)*pow(np.e,-p[1])/factorial(x)

            ## A list of the initial guess for the parameters.
            p0 = [float(self.__n_f), float(self.__Lambda_est)]

            ## The error function to perform the fitting.
            errfunc = lambda p, x, y: fitfunc(p, x) - y # Distance to the target function

            ## The fitted parameters.
            p1 = None

            ## Did the fit succeed?
            success = None

            # Use the least square method from optimize to perform a check.
            # We won't actually use this...
            p1, success = optimize.leastsq(errfunc, p0[:], args=(self.__bins[:-1], self.__n))

            if success == 1:
                lg.info(" * FIT SUCCEEDED!")

            lg.info(" * Fitted values [Scale, \\lambda]:")
            lg.info(p1)
            lg.info(" *")

            # Use the estimated parameters from the Bayesian method.
            p_est = [float(self.__n_f), float(self.__Lambda_est)]

            # Plot the Poisson distribution from the estimated values.
            plt.bar(self.__bins, fitfunc(p_est, self.__bins), color='#CCCCCC')

            # Plot the Poisson distribution with the fitted values.
            #plt.bar(self.__bins, fitfunc(p1, self.__bins), color='#CCCCCC')

            # The expected y values from the estimated parameters.
            y_est = fitfunc(p_est, self.__bins)

            # The expected y values from the fitted parameters.
            y_fit = fitfunc(p1, self.__bins)

            # The distances from the predicted values - Bayesian estimates.
            ds_est = errfunc(p_est, self.__bins[:-1], self.__n)

            # The distances from the predicted values - fitted estimates.
            ds_fit = errfunc(p1, self.__bins[:-1], self.__n)

            for i, binval in enumerate(self.__bins[:-1]):
                lg.info(" * | % 3d | % 3d | % 3d | % 6.3f | % 10.8f | % 6.3f | % 10.8f |" %
                    (i, binval, self.__n[i], y_est[i], ds_est[i], y_fit[i], ds_fit[i]))

        # Firstly, we'll need an array of the bin centres to put the points
        # at (rather than the bin edges). Note the slicing of the bin edges
        # array to get the N bins (as otherwise we'd have N+1 bin edges).
        self.bins_centres = 0.5*(self.__bins[1:]+self.__bins[:-1])

        #lg.info(" * The bin centres:")
        #lg.info(self.bins_centres)
        #lg.info(" *")

        # Some bins will be empty. To avoid plotting these, we can replace
        # the contents of that bin with "nan" (not a number).
        # matplotlib then cleverly skips over these, leaving the x axis
        # clean and shiny.
        self.__n = [np.nan if x==0 else float(x) for x in self.__n]

        # Calculate the errors on the number of clusters counted (Poisson).
        self.__err = np.sqrt(self.__n)

        # Calculate the Chi^2 values for the estimated distribution.
        chi2_est, n_deg_est, chi2_div_est = chi2(self.__n, y_est, 1)

        lg.info(" * Estimated distribution (\hat{\Lambda}):")
        lg.info(" *--> \Chi^2      = % 7.5f" % (chi2_est))
        lg.info(" *--> N_freedom   = % d" % (n_deg_est))
        lg.info(" *")

        # Calculate the Chi^2 values for the fitted distribution.
        chi2_fit, n_deg_fit, chi2_div_fit = chi2(self.__n, y_fit, 1)
        lg.info(" * Fitted distribution:")
        lg.info(" *--> \Chi^2      = % 7.5f" % (chi2_fit))
        lg.info(" *--> N_freedom   = % d" % (n_deg_fit))
        lg.info(" *")

        # Plot the real data points as an "errorbar" plot
        plt.errorbar(self.bins_centres, \
                     self.__n, \
                     fmt='d', \
                     color='black', \
                     yerr=self.__err, \
                     ecolor='black', \
#                     capthick=2, \
                     elinewidth=1)

        # Save the figure.

        # Custom plot limits if required.
        #plt.ylim([0, 16])
        #plt.xlim([0, 20])

        self.plot.savefig(self.__output_path + "/%s.png" % (self.__name))

        self.plot.savefig(self.__output_path + "/%s.ps" % (self.__name))

        return self.__Lambda_est, self.__Lambda_est_err, chi2_est, n_deg_est
    def __init__(self, data_points):

        ## A list of DataPoints.
        self.__dps = data_points

        # Calculate all of the properties of the data points.

        # Get the estimated attenuation coefficient and B_0 by fitting
        # ln(B_i) vs. d_i to a straight line.

        ## The estimate of the attenuation coefficient.
        self.__mu_est = None

        ## The estimated error on the attenuation coefficient (MLL).
        self.__mu_est_err_mll = None

        ## The estimate of the mean free path.
        self.__mfp_est = None

        ## The estimated error on the mean free path (MLL).
        self.__mfp_est_err_mll = None

        ## The estimated initial attempts (fit).
        self.__B_0_est = None

        # First, let's use the curve_fit function to fit the points
        # to a straight line.

        ## An array of the x values (thicknesses).
        self.__xs = np.array(self.get_thicknesses())

        ## An array of the y values (thicknesses).
        self.__ys = np.array(self.get_log_of_successes())

        ## A list of the estimated parameters, m and c.
        self.__parameter_estimates = None

        ## The covariance matrix of the parameter estimates.
        self.__covariance_matrix = None

        # Perform the fitting.
        self.__parameter_estimates, self.__covariance_matrix = curve_fit(straight_line, self.__xs, self.__ys)

        # Assign the estimate values and errors.
        self.__mu_est = -1.0 * self.__parameter_estimates[0]
        #
        self.__mfp_est = 1.0 / self.__mu_est
        #
        self.__B_0_est = np.exp(self.__parameter_estimates[1])

        # Now use the Maximum Log Likelihood method to estimate the error.

        # Loop over the data points

        sum_of_terms = 0.0

        lg.info(" *")
        lg.info(" * Looping over the data points:")
        lg.info(" *")
        for dp in sorted(self.__dps):
            lg.info(" * | d_i = % 5.2f [mm] | B_i = % 8d |" % (dp.get_thickness(), dp.get_count()))

            B_i_times_B_0 = dp.get_count() * self.__B_0_est

            B_0_minus_B_i = self.__B_0_est - dp.get_count()

            count_frac = float(B_i_times_B_0) / float(B_0_minus_B_i)

            d_i_squared = dp.get_thickness() * dp.get_thickness()

            d_i_squared_times_count_frac = d_i_squared * count_frac

            sum_of_terms += d_i_squared_times_count_frac

            lg.info(" * |-----------------------------------|")
            lg.info(" * | d_i^{2}   = %f" % (d_i_squared))
            lg.info(" * | |")
            lg.info(" * | | B_i * B_0 = %d" % (B_i_times_B_0))
            lg.info(" * | | B_0 - B_i = %d" % (B_0_minus_B_i))
            lg.info(" * | |-->Y/Z     = %f" % (count_frac))
            lg.info(" * | |")
            lg.info(" * | *-->X*Y/Z   = %f" % (d_i_squared_times_count_frac))
            lg.info(" * |")
        lg.info(" *")
        lg.info(" * Sum of terms = %f [mm^{2}] " % (sum_of_terms))
        lg.info(" *")
        #
        self.__mu_est_err_mll = 1.0/(np.sqrt(sum_of_terms))
        #
        self.__mu_est_err_mll_pc = 100.0 * (self.__mu_est_err_mll/self.__mu_est)
        #
        self.__mfp_est_err_mll = (1.0/(self.__mu_est * self.__mu_est)) * self.__mu_est_err_mll
        #
        self.__mfp_est_err_mll_pc = 100.0 * (self.__mfp_est_err_mll / self.__mfp_est)
        #
        lg.info(" * 1/sqrt(sum)  = %f [mm^{-1}]" % (self.__mu_est_err_mll))
        lg.info(" *")

        lg.info(" *")
        lg.info(" * from curve_fit:")
        lg.info(" *")
        lg.info(" *--> \mu (MLL)      = (% 10.5f \pm % 10.5f) [mm^{-1}] % 6.2f %%" % \
            (self.__mu_est, self.__mu_est_err_mll, self.__mu_est_err_mll_pc))
        lg.info(" *")
        lg.info(" *--> <x> = 1 / \mu  = (% 10.5f \pm % 10.5f) [mm]      % 6.2f %%" % \
            (self.__mfp_est, self.__mfp_est_err_mll, self.__mfp_est_err_mll_pc))
        lg.info(" *")
        lg.info(" *--> B_0            = % 10.2f particles" % (self.__B_0_est))
        lg.info(" *")

        # Calculate the Chi^2 values for the estimated distribution.
        #chi2_est, n_deg_est, chi2_div_est = chi2(self.get_successes(), self.get_predicted_successes(), 2)
        self.__chi_squared_value, self.__chi_squared_dof, chi2_div_est = \
            chi2(self.get_log_of_successes(), self.get_predicted_log_of_successes(), 2)

        lg.info(" * Estimated distribution (\hat{\mu}, \hat{B_{0}}):")
        lg.info(" *--> \Chi^2      = % 7.5f" % (self.__chi_squared_value))
        lg.info(" *--> N_freedom   = % d" % (self.__chi_squared_dof))
        lg.info(" *")