def METHOD_NAME(X, Y, Z, levels):
    """
    Calculate contour lines
    """
    from contourpy import contour_generator
    # Preparation of values and the creating of contours is
    # adapted from MPL with some adjustments.
    X = np.asarray(X, dtype=np.float64)
    Y = np.asarray(Y, dtype=np.float64)
    Z = np.asarray(Z, dtype=np.float64)
    zmin, zmax = Z.min(), Z.max()
    cgen = contour_generator(
        X, Y, Z, name="mpl2014", corner_mask=False, chunk_size=0
    )
    if isinstance(levels, int):
        from mizani.breaks import breaks_extended
        levels = breaks_extended(n=levels)((zmin, zmax))
    # The counter_generator gives us a list of vertices that
    # represent all the contour lines at that level. There
    # may be 0, 1 or more vertices at a level. Each one of
    # these we call a piece, and it represented as an nx2 array.
    #
    # We want x-y values that describe *all* the contour lines
    # in tidy format. Therefore each x-y vertex has a
    # corresponding level and piece id.
    segments = []
    piece_ids = []
    level_values = []
    start_pid = 1
    for level in levels:
        vertices, _ = cgen.create_contour(level)
        for pid, piece in enumerate(vertices, start=start_pid):
            n = len(piece)  # pyright: ignore
            segments.append(piece)
            piece_ids.append(np.repeat(pid, n))
            level_values.append(np.repeat(level, n))
            start_pid = pid + 1
    # Collapse the info and make it fit for dataframe columns
    if segments:
        x, y = np.vstack(segments).T
        piece = np.hstack(piece_ids)
        level = np.hstack(level_values)
    else:
        x, y = [], []
        piece = []
        level = []
    data = pd.DataFrame(
        {
            "x": x,
            "y": y,
            "level": level,
            "piece": piece,
        }
    )
    return data