def debug_test_img(self):
        img = cv2.imread("./camera_cal/calibration1.jpg")
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray = self.undistort(gray)
        DEBUG.save_img(gray, "test_undistorted_calibration0.jpg")

        img = cv2.imread("./test_images/test1.jpg")
        img = self.undistort(img)
        DEBUG.save_img(img, "test_undistorted_test1.jpg")
def progressive_binary(saturation_warped, grayscale_warped, saturation_warped_threshold):
    # I think I'm supposed to be applying the threshold on the raw saturation value
    #raise Exception("I'm not certain I'm even supposed to be applying sobel on top of the saturation value.")

    # Sobel kernel of 3 for bottom half
    sobel_kernel = 3
    sobelx_kernel_3_s = cv2.Sobel(saturation_warped, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobelx_kernel_3_gray = cv2.Sobel(grayscale_warped, cv2.CV_64F, 1, 0, ksize=sobel_kernel)

    # right now, no matter what i do I can't find the top mark in the test2

    sobel_kernel = 5
    sobelx_kernel_5_s = cv2.Sobel(saturation_warped, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobelx_kernel_5_gray = cv2.Sobel(grayscale_warped, cv2.CV_64F, 1, 0, ksize=sobel_kernel)

    DEBUG.save_img(grayscale_warped, "gray.png")
    DEBUG.save_img(sobelx_kernel_5_gray, "saturatsobelx_kernel_5_gray.png")
    DEBUG.save_img(sobelx_kernel_3_gray, "sobelx_kernel_3_gray.png")

    #sobel_kernel = 9
    #sobelx_kernel_9_s = cv2.Sobel(saturation_warped, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    #sobelx_kernel_9_gray = cv2.Sobel(grayscale_warped, cv2.CV_64F, 1, 0, ksize=sobel_kernel)

    # TODO: don't just use the saturation binary. Also use the grayscale one. 
    # the two of them are better toghether.
    # https://classroom.udacity.com/nanodegrees/nd013/parts/fbf77062-5703-404e-b60c-95b78b2f3f9e/modules/2b62a1c3-e151-4a0e-b6b6-e424fa46ceab/lessons/0bcd97c5-66f3-495b-9fe2-3f9f541bae25/concepts/a1b70df9-638b-46bb-8af0-12c43dcfd0b4

    # bottom_half = img[img.shape[0]//2:,:]
    height = saturation_warped.shape[0]

    binary_sobelx = np.zeros_like(saturation_warped)
    apply_filter(sobelx_kernel_3_s, sobelx_kernel_3_gray, height//2, height, binary_sobelx, 30, 50)
    apply_filter(sobelx_kernel_3_s, sobelx_kernel_5_gray, height//4, height//2, binary_sobelx, 20, 100)
    apply_filter(sobelx_kernel_3_s, sobelx_kernel_5_gray, 0, height//4, binary_sobelx, 20, 500)
    return binary_sobelx
def process_img(img, name):

    #
    # WARP TO NEW PERSPECTIVE
    #

    source_points = get_image_points()
    img = camera.undistort(img)
    DEBUG.save_img_with_path(img, source_points, name)

    source_points = np.array(source_points, np.float32)
    destination_points = np.array(get_destination_points(), np.float32)
    M = cv2.getPerspectiveTransform(source_points, destination_points)
    warped = cv2.warpPerspective(img, M, camera.viewport_size(), flags=cv2.INTER_LINEAR)
    DEBUG.save_img(warped, "warped_" + name)

    #
    # CREATE BINARY IMAGE
    #

    # Create grayscale image
    grayscale_warped = cv2.cvtColor(warped, cv2.COLOR_RGB2GRAY)
    hls_warped = cv2.cvtColor(warped, cv2.COLOR_RGB2HLS)
    saturation_warped = hls_warped[:,:,2]
    l_warped = hls_warped[:,:,1]
    #DEBUG.save_img(saturation_warped, "warped_saturation_" + name)
    saturation_warped_threshold = np.zeros_like(saturation_warped)

    # edge detection on a combination of saturation and grayscale.
    # saturation is mostly useful on yellow. So this is our best bet.
    binary_sobelx = progressive_binary(saturation_warped, grayscale_warped, saturation_warped_threshold)
    #DEBUG.save_img(binary_sobelx, "binary_" + name)

    # find the peaks in the bottom half of the image. This is the starting
    # point for finding the lane.
    histogram = bottom_half_histogram(binary_sobelx)

    max_first_half = histogram[:len(histogram)//2].argmax()
    max_second_half = histogram[len(histogram)//2:].argmax() + len(histogram)//2
    annotated_image, left_path = find_lines(max_first_half, binary_sobelx)
    #DEBUG.save_img(annotated_image, "annotated_output_left_" + name + ".png")
    annotated_image, right_path = find_lines(max_second_half, binary_sobelx)
    #DEBUG.save_img(annotated_image, "annotated_output_right_" + name + ".png")

    #
    # Fit polylines to the detected lane points
    # 

    # Generate x and y values for plotting in projected space
    fit_poly_left = np.polyfit([x[1] for x in left_path], [x[0] for x in left_path], 2)
    fit_poly_right = np.polyfit([x[1] for x in right_path], [x[0] for x in right_path], 2)
    ploty = np.linspace(0, binary_sobelx.shape[0]-1, binary_sobelx.shape[0])
    left_fitx = fit_poly_left[0]*ploty**2 + fit_poly_left[1]*ploty + fit_poly_left[2]
    right_fitx = fit_poly_right[0]*ploty**2 + fit_poly_right[1]*ploty + fit_poly_right[2]
    
    # Convert left path back to original space
    path_left = list(zip(left_fitx, ploty))
    path_left = np.array([(path_left)], dtype=np.float32)
    reverse_transform = cv2.getPerspectiveTransform(destination_points, source_points)
    converted_left_path = cv2.perspectiveTransform(path_left, reverse_transform)
    
    # Convert right path back to original space
    path_right = list(zip(right_fitx, ploty))
    path_right = np.array([(path_right)], dtype=np.float32)
    converted_right_path = cv2.perspectiveTransform(path_right, reverse_transform)

    # Draw bounding box around the lane area
    overlay_image = np.zeros_like(img)
    bounding_array = np.concatenate( (converted_left_path[0], np.flipud(converted_right_path[0])) )
    bounding_box = np.array([bounding_array], dtype=np.int32)
    overlay_image = cv2.fillPoly(overlay_image, bounding_box, (0,255, 0))

    # this works fine
    #DEBUG.save_img(overlay_image, "overlay_image_" + name + ".png")
    result_image = cv2.addWeighted(img, 1, overlay_image, 0.3, 0)
    #DEBUG.save_img(result_image, "zoutput_" + name + ".png")

    # use plot to get us an image
    path_left = list(zip(left_fitx, ploty))
    path_left = np.array([(path_left)], dtype=np.float32)
    converted_left_path = cv2.perspectiveTransform(path_left, reverse_transform)
    path_right = list(zip(right_fitx, ploty))
    path_right = np.array([(path_right)], dtype=np.float32)
    converted_right_path = cv2.perspectiveTransform(path_right, reverse_transform)
    
    #
    # Curvature & offset in lane
    #

    # Calculate values
    left_curvature = calculate_curvature_meters(left_path)
    right_curvature = calculate_curvature_meters(right_path)
    average_curvature = (left_curvature + right_curvature) / 2.0
    offset = calculate_offset(left_path[0][0], right_path[0][0], binary_sobelx.shape[1])

    # Annotate the image
    font = cv2.FONT_HERSHEY_SIMPLEX
    text = 'Radius of curvature = {}m'.format(int(average_curvature))
    text2 = 'Offset from center = {}m'.format(offset)
    result_image = cv2.putText(result_image, text, (100,100), font, 1,(255,255,255),2,cv2.LINE_AA)
    result_image = cv2.putText(result_image, text2, (100,150), font, 1,(255,255,255),2,cv2.LINE_AA)
    DEBUG.save_img(result_image, "zzzfinal_output_" + name + ".png")
    return result_image