Determine the location of where outliers begin in a 1D histogram.

    When the moving average falls below the limit, we consider the outliers to have begun.

    To determine the location of outliers:

    - Calculate the moving average for number_of_values_to_search_ahead values.
    - First, the moving average must go above the limit at least once to guard against a random cut
      in a low pt bin causing most of the data to be cut out.
    - Next, we look for a consecutive number of entries below limit_of_number_of_values_below_threshold.
    - If we meet that condition, we have found the index where the outliers begin. We then return the ROOT
      bin index of the value.
    - If not, we return -1.

    Note:
        The index returned is when the moving average first drops below the threshold for a moving average
        calculated with that bin at the center. This is somewhat different from a standard moving average
        calculation which would only look forward in the array.

    Args:
        hist: Histogram to be checked for outliers.
        moving_average_threshold: Value of moving average under which we consider the moving average
            to be 0. Default: 2.
        number_of_values_to_search_ahead: Number of values to search ahead in the array when calculating
            the moving average. Default: 5.
        limit_of_number_of_values_below_threshold: Number of consecutive bins below the threshold to be considered
            the beginning of outliers. Default: None, which will correspond to number_of_values_to_search_ahead - 1.
    Returns:
        ROOT (ie 1-indexed) index of the histogram axes where the outliers begin.

def _determine_outliers_index(hist: Hist,
                              moving_average_threshold: float = 1.0,
                              number_of_values_to_search_ahead: int = 5,
                              limit_of_number_of_values_below_threshold: int = None) -> int:
    """ Determine the location of where outliers begin in a 1D histogram.

    When the moving average falls below the limit, we consider the outliers to have begun.

    To determine the location of outliers:

    - Calculate the moving average for number_of_values_to_search_ahead values.
    - First, the moving average must go above the limit at least once to guard against a random cut
      in a low pt bin causing most of the data to be cut out.
    - Next, we look for a consecutive number of entries below limit_of_number_of_values_below_threshold.
    - If we meet that condition, we have found the index where the outliers begin. We then return the ROOT
      bin index of the value.
    - If not, we return -1.

    Note:
        The index returned is when the moving average first drops below the threshold for a moving average
        calculated with that bin at the center. This is somewhat different from a standard moving average
        calculation which would only look forward in the array.

    Args:
        hist: Histogram to be checked for outliers.
        moving_average_threshold: Value of moving average under which we consider the moving average
            to be 0. Default: 2.
        number_of_values_to_search_ahead: Number of values to search ahead in the array when calculating
            the moving average. Default: 5.
        limit_of_number_of_values_below_threshold: Number of consecutive bins below the threshold to be considered
            the beginning of outliers. Default: None, which will correspond to number_of_values_to_search_ahead - 1.
    Returns:
        ROOT (ie 1-indexed) index of the histogram axes where the outliers begin.
    """
    # Validation
    import ROOT
    if isinstance(hist, (ROOT.TH2, ROOT.TH3, ROOT.THnBase)):
        raise ValueError(
            f"Given histogram '{hist.GetName()}' of type {type(hist)}, but can only"
            " determine the outlier location of a 1D histogram. Please project to"
            " the particle level axis first."
        )

    if limit_of_number_of_values_below_threshold is None:
        # In principle, this could be another value. However, this is what was used in the previous outliers
        # removal implementation.
        limit_of_number_of_values_below_threshold = number_of_values_to_search_ahead - 1

    # It is much more convenient to work with a numpy array.
    hist_to_check = histogram.Histogram1D.from_existing_hist(hist)

    # Calculate the moving average for the entire axis, looking ahead including the current bin + 4 = 5 ahead.
    number_of_values_to_search_ahead = 5
    moving_average = utils.moving_average(hist_to_check.y, n = number_of_values_to_search_ahead)

    #logger.debug(f"y: {hist_to_check.y}")
    #logger.debug(f"moving_average: {moving_average}")

    cut_index = _determine_outliers_for_moving_average(
        moving_average = moving_average,
        moving_average_threshold = moving_average_threshold,
        number_of_values_to_search_ahead = number_of_values_to_search_ahead,
        limit_of_number_of_values_below_threshold = limit_of_number_of_values_below_threshold,
    )

    if cut_index != -1:
        # ROOT histograms are 1 indexed, so we add another 1.
        cut_index += 1

    return cut_index