def choose_file():
    """Get graph & fit fcn from ROOT file via tkFileDialog, plot them on the canvas"""
    ftypes = [('ROOT files', '*.root'), ('All files', '*')]
    dlg = tkFileDialog.Open(filetypes=ftypes)
    fl = dlg.show()
    if fl != '':
        root_file = ROOT.TFile(fl, "READ")
        gr = root_file.Get(graph_entry.get())
        fn = root_file.Get(fn_entry.get())
        if gr and fn:
            tkMessageBox.showinfo("Got graph & fit", "Got graph %s and function %s" % (graph_entry.get(), fn_entry.get()))
            # store xy points so properly drawn when canvas updated
            global graph_x, graph_errx, graph_y, graph_erry
            graph_x, graph_y = cu.get_xy(gr)
            graph_errx, graph_erry = cu.get_exey(gr)
            new_params = [fn.GetParameter(i) for i in xrange(fn.GetNumberFreeParameters())]
            for slider, box, param in zip(sliders, multiplier_boxes, new_params):
                set_slider_box_values(slider, box, param)
        else:
            if not gr:
                tkMessageBox.showwarning("No graph", "Graph with name %s does not exist" % graph_entry.get())
            if not fn:
                tkMessageBox.showwarning("No function", "Function with name %s does not exist" % graph_entry.get())
def setup_fit(graph, function, absetamin, absetamax, outputfile):
    """Setup for fitting (auto-calculate sensible range).

    Returns a sub-graph of only sensible points (chop off turnover at low pT,
    and any high pT tail), along with a corresponding fit function
    whose range has been set to match the sub graph.
    """
    print 'Setting up fit'
    xarr, yarr = cu.get_xy(graph)
    exarr, eyarr = cu.get_exey(graph)
    # first test out graph isn't empty
    if len(xarr) == 0:
        raise RuntimeError("graph in setup_fit() is empty")

    fit_max = max(xarr)  # Maxmimum pt for upper bound of fit
    # fit_min = 10 if absetamin > 2.9 else 10
    fit_min = min(xarr) # Minimum pt for lower bound of fit

    # For lower bound of fit, use either fit_min or the pt
    # of the maximum correction value, whichever has the larger pT.
    # Check to make sure it's not the last point on the graph
    # (e.g. if no turnover), in which case just use the default fit_min
    # Then find the index of the closest corresponding value in xarr (since
    # fit_min could correspond to a pT that isn't in the list of x-points)
    # Note that we want the maximum in the first half of the graph to avoid
    # the 'flick' at high pT in HF
    # JOE EDIT: (un)comment the next 4 lines to (not) have the flick
    max_corr = max(yarr[:len(yarr) / 2])
    max_corr_ind = yarr.index(max_corr)
    max_corr_pt = xarr[max_corr_ind]
    fit_min = max(fit_min, max_corr_pt) if (max_corr_pt != xarr[-1]) and (max_corr_pt != xarr[-1]) else fit_min
    min_ind = next(i for i, x in enumerate(xarr) if x >= fit_min)

    # To find upper limit of fit, we need to detect if & where there is a turnover
    # (i.e. where the gradient goes from -ve to +ve)
    # This is made difficult by the fact the graph may be 'noisy' and simply
    # taking the gradient may not be enough (and give multiple points where
    # the gradient changes). To counter this, we smooth the gradient by
    # averaging over several points
    def moving_average(arr, n):
        """Returns a np.array of moving-averages of array arr, where each
        point in the rerurned array is the average of the consecutive n points
        in the original array.

        By definition, this will return an array of length len(arr) + 1 - n
        """
        return np.array([np.mean(arr[i:i+n]) for i in range(0, len(arr) - n + 1)])

    def calc_crossing(arr):
        """Calculate value at which value the array crosses 0.

        Looks at points in groups of 4, and finds the smallest group
        where the first 2 points < 0, and the next 2 points > 0.

        This ignores values which peak above 0 for 1 point.

        Returns the array (index, value) of the point closest to 0.
        """
        for i in range(2, len(arr)):
            group = np.concatenate((-1 * arr[i-2: i], arr[i: i+2]))
            if np.all(group > 0):
                return i - 2 + list(group).index(np.min(group)), np.min(group)
        return None, None

    grad = np.gradient(yarr, 1)
    n_sample = 5
    intercept_ind, intercept = None, None
    # keep incrementing the smooting value until we get a clean intercept
    while not intercept_ind and not intercept:
        x_ave = moving_average(xarr, n_sample)
        grad_ave = moving_average(grad, n_sample)
        intercept_ind, intercept = calc_crossing(grad_ave)
        n_sample += 1
        # quit if we've got stuck
        if n_sample == 12:
            break

    if intercept and intercept_ind:
        print 'Found minima'
        print 'Smoothing param:', n_sample
        # find closest x value to intercept
        max_ind, fit_max = closest_element(xarr, x_ave[intercept_ind])
    else:
        print '! Could not find minima, falling back to just using min()'
        # Here we assume a failure to find any minima, so fallback and use
        # the smallest point.
        max_ind = list(yarr).index(min(yarr))
        fit_max = xarr[max_ind]
    if fit_min > fit_max:
        raise RuntimeError('fit_min > fit_max! (%f > %f)' % (fit_min, fit_max))

    print "Correction fn fit range:", fit_min, fit_max

    # Generate a correction function with suitable range
    this_fit = function.Clone(function.GetName() + 'eta_%g_%g' % (absetamin, absetamax))
    this_fit.SetRange(fit_min, fit_max)

    # Make a sub-graph with only the points used for fitting
    # Do not user graph.RemovePoint()! It doesn't work, and only removes every other point
    # Instead make a graph with the bit of array we want
    fit_graph = ROOT.TGraphErrors(max_ind + 1 - min_ind,
                                  np.array(xarr[min_ind:max_ind + 1]),
                                  np.array(yarr[min_ind:max_ind + 1]),
                                  np.array(exarr[min_ind:max_ind + 1]),
                                  np.array(eyarr[min_ind:max_ind + 1]))
    fit_graph.SetName(graph.GetName() + "_fit")

    return fit_graph, this_fit
def setup_fit(graph, function, absetamin, absetamax, outputfile):
    """Setup for fitting (auto-calculate sensible range).

    Returns a sub-graph of only sensible points (chop off turnover at low pT,
    and any high pT tail), along with a corresponding fit function
    whose range has been set to match the sub graph.
    """
    print 'Setting up fit'
    xarr, yarr = cu.get_xy(graph)
    exarr, eyarr = cu.get_exey(graph)
    # first test out graph isn't empty
    if len(xarr) == 0:
        raise RuntimeError("graph in setup_fit() is empty")

    fit_max = max(xarr)  # Maxmimum pt for upper bound of fit

    # fit_min = 10 if absetamin > 2.9 else 10
    fit_min = min(xarr)  # Minimum pt for lower bound of fit

    # For lower bound of fit, use either fit_min or the pt
    # of the maximum correction value, whichever has the larger pT.
    # Check to make sure it's not the last point on the graph
    # (e.g. if no turnover), in which case just use the default fit_min
    # Then find the index of the closest corresponding value in xarr (since
    # fit_min could correspond to a pT that isn't in the list of x-points)
    # Note that we want the maximum in the first half of the graph to avoid
    # the 'flick' at high pT in HF
    # JOE EDIT: (un)comment the next 4 lines to (not) have the flick
    max_corr = max(yarr[:len(yarr) / 2])
    max_corr_ind = yarr.index(max_corr)
    max_corr_pt = xarr[max_corr_ind]
    fit_min = max(fit_min, max_corr_pt) if (max_corr_pt != xarr[-1]) and (
        max_corr_pt != xarr[-1]) else fit_min
    min_ind = next(i for i, x in enumerate(xarr) if x >= fit_min)

    # To find upper limit of fit, we need to detect if & where there is a turnover
    # (i.e. where the gradient goes from -ve to +ve)
    # This is made difficult by the fact the graph may be 'noisy' and simply
    # taking the gradient may not be enough (and give multiple points where
    # the gradient changes). To counter this, we smooth the gradient by
    # averaging over several points
    def moving_average(arr, n):
        """Returns a np.array of moving-averages of array arr, where each
        point in the rerurned array is the average of the consecutive n points
        in the original array.

        By definition, this will return an array of length len(arr) + 1 - n
        """
        return np.array(
            [np.mean(arr[i:i + n]) for i in range(0,
                                                  len(arr) - n + 1)])

    def calc_crossing(arr):
        """Calculate value at which value the array crosses 0.

        Looks at points in groups of 4, and finds the smallest group
        where the first 2 points < 0, and the next 2 points > 0.

        This ignores values which peak above 0 for 1 point.

        Returns the array (index, value) of the point closest to 0.
        """
        for i in range(2, len(arr)):
            group = np.concatenate((-1 * arr[i - 2:i], arr[i:i + 2]))
            if np.all(group > 0):
                return i - 2 + list(group).index(np.min(group)), np.min(group)
        return None, None

    grad = np.gradient(yarr, 1)
    n_sample = 5
    intercept_ind, intercept = None, None
    # keep incrementing the smooting value until we get a clean intercept
    while not intercept_ind and not intercept:
        x_ave = moving_average(xarr, n_sample)
        grad_ave = moving_average(grad, n_sample)
        intercept_ind, intercept = calc_crossing(grad_ave)
        n_sample += 1
        # quit if we've got stuck
        if n_sample == 12:
            break

    if intercept and intercept_ind:
        print 'Found minima'
        print 'Smoothing param:', n_sample
        # find closest x value to intercept
        max_ind, fit_max = closest_element(xarr, x_ave[intercept_ind])
    else:
        print '! Could not find minima, falling back to just using min()'
        # Here we assume a failure to find any minima, so fallback and use
        # the smallest point.
        max_ind = list(yarr).index(min(yarr))
        fit_max = xarr[max_ind]

    #############
    # JOE_HACKs #
    # 1. to fix the maximum pt for all fits (high pt saturation issue)
    # helps ease things into the right solution space
    # if fit_max > 650.0:
    #     print "*** WARNING: about to apply a JOE_HACK ***"
    #     print "*** lowers the upper limit of the fits to 650GeV ***"
    #     fit_max = 650.0
    # 2. to change the auto settings for a troublesome eta bin
    #if absetamin == 2.5:
    #    print "*** WARNING: about to apply a JOE_HACK ***"
    #    print "*** messing with fit limits for 2.500<|eta|<2.964 ***"
    #    max_ind = 80
    #    fit_max = xarr[max_ind]
    #    print max_ind
    #    print fit_max
    #    fit_min = 40.0
    #    min_ind = 0
    if absetamin == 2.964:
        print "* WARNING: about to apply a JOE_HACK *"
        print "* messing with fit limits for 2.964<|eta|<3.489 *"
        max_ind = 17
        fit_max = xarr[max_ind]
        print max_ind
        print fit_max
        fit_min = 40.0
        min_ind = 0
    if absetamin == 3.489:
        print "* WARNING: about to apply a JOE_HACK *"
        print "* messing with fit limits for 3.489<|eta|<4.191 *"
        max_ind = 17
        fit_max = xarr[max_ind]
        print max_ind
        print fit_max
        fit_min = 40.0
        min_ind = 0
    if absetamin == 4.191:
        print "* WARNING: about to apply a JOE_HACK *"
        print "* messing with fit limits for 4.191<|eta|<5.191 *"
        max_ind = 17
        fit_max = xarr[max_ind]
        print max_ind
        print fit_max
        fit_min = 40.0
        min_ind = 0
    ###############
    ###############

    if fit_min > fit_max:
        raise RuntimeError('fit_min > fit_max! (%f > %f)' % (fit_min, fit_max))

    print "Correction fn fit range:", fit_min, fit_max

    # Generate a correction function with suitable range
    this_fit = function.Clone(function.GetName() + 'eta_%g_%g' %
                              (absetamin, absetamax))
    this_fit.SetRange(fit_min, fit_max)

    # Make a sub-graph with only the points used for fitting
    # Do not user graph.RemovePoint()! It doesn't work, and only removes every other point
    # Instead make a graph with the bit of array we want
    fit_graph = ROOT.TGraphErrors(max_ind + 1 - min_ind,
                                  np.array(xarr[min_ind:max_ind + 1]),
                                  np.array(yarr[min_ind:max_ind + 1]),
                                  np.array(exarr[min_ind:max_ind + 1]),
                                  np.array(eyarr[min_ind:max_ind + 1]))
    fit_graph.SetName(graph.GetName() + "_fit")

    return fit_graph, this_fit