def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255):
    image_row, image_col = image.shape[0], image.shape[1]
    # gaussian_filter
    gaussian_out = img_convolve(image, gen_gaussian_kernel(9, sigma=1.4))
    # get the gradient and degree by sobel_filter
    sobel_grad, sobel_theta = sobel_filter(gaussian_out)
    gradient_direction = np.rad2deg(sobel_theta)
    gradient_direction += PI

    dst = np.zeros((image_row, image_col))

    for row in range(1, image_row - 1):
        for col in range(1, image_col - 1):
            direction = gradient_direction[row, col]

            if (
                0 <= direction < 22.5
                or 15 * PI / 8 <= direction <= 2 * PI
                or 7 * PI / 8 <= direction <= 9 * PI / 8
            ):
                w = sobel_grad[row, col - 1]
                e = sobel_grad[row, col + 1]
                if sobel_grad[row, col] >= w and sobel_grad[row, col] >= e:
                    dst[row, col] = sobel_grad[row, col]

            elif (PI / 8 <= direction < 3 * PI / 8) or (
                9 * PI / 8 <= direction < 11 * PI / 8
            ):
                sw = sobel_grad[row + 1, col - 1]
                ne = sobel_grad[row - 1, col + 1]
                if sobel_grad[row, col] >= sw and sobel_grad[row, col] >= ne:
                    dst[row, col] = sobel_grad[row, col]

            elif (3 * PI / 8 <= direction < 5 * PI / 8) or (
                11 * PI / 8 <= direction < 13 * PI / 8
            ):
                n = sobel_grad[row - 1, col]
                s = sobel_grad[row + 1, col]
                if sobel_grad[row, col] >= n and sobel_grad[row, col] >= s:
                    dst[row, col] = sobel_grad[row, col]

            elif (5 * PI / 8 <= direction < 7 * PI / 8) or (
                13 * PI / 8 <= direction < 15 * PI / 8
            ):
                nw = sobel_grad[row - 1, col - 1]
                se = sobel_grad[row + 1, col + 1]
                if sobel_grad[row, col] >= nw and sobel_grad[row, col] >= se:
                    dst[row, col] = sobel_grad[row, col]

            if dst[row, col] >= threshold_high:
                dst[row, col] = strong
            elif dst[row, col] <= threshold_low:
                dst[row, col] = 0
            else:
                dst[row, col] = weak

    for row in range(1, image_row):
        for col in range(1, image_col):
            if dst[row, col] == weak:
                if 255 in (
                    dst[row, col + 1],
                    dst[row, col - 1],
                    dst[row - 1, col],
                    dst[row + 1, col],
                    dst[row - 1, col - 1],
                    dst[row + 1, col - 1],
                    dst[row - 1, col + 1],
                    dst[row + 1, col + 1],
                ):
                    dst[row, col] = strong
                else:
                    dst[row, col] = 0

    return dst