can you help me fix error in warp_perspective(img, H, target_shape)
    147     warped_image = np.zeros((w, h, 3), dtype=np.uint8) # Swap w and h
    148     for i in range(3):
--> 149         warped_image[..., i][valid_target_coordinates[1], valid_target_coordinates[0]] = img[..., i][valid_source_coordinates[1], valid_source_coordinates[0]]
    150 
    151     return warped_image

IndexError: index 752 is out of bounds for axis 0 with size 653, The full code is here: "import numpy as np
import cv2
import os
import glob
import matplotlib.pyplot as plt
import time


def feature_extraction(sub_images, method="SIFT"):
    if method == "SIFT":
        keypoint_extractor = cv2.xfeatures2d.SIFT_create()
    elif method == "SURF":
        keypoint_extractor = cv2.xfeatures2d.SURF_create()
    elif method == "ORB":
        keypoint_extractor = cv2.ORB_create()

    keypoints = []
    descriptors = []

    for sub_image in sub_images:
        keypoint, descriptor = keypoint_extractor.detectAndCompute(sub_image, None)
        keypoints.append(keypoint)
        descriptors.append(descriptor)

    return keypoints, descriptors


def feature_matching(descriptors, matcher_type="BF"):
    if matcher_type == "BF":
        matcher = cv2.BFMatcher()

    matches = []
    for i in range(1, len(descriptors)):
        match = matcher.knnMatch(descriptors[0], descriptors[i], k=2)
        matches.append(match)
        
    return matches

def compute_homography_matrix(src_pts, dst_pts):
    A = []

    for i in range(4):
        u1, v1 = src_pts[i]
        u2, v2 = dst_pts[i]

        A.append([-u1, -v1, -1, 0, 0, 0, u1 * u2, v1 * u2, u2])
        A.append([0, 0, 0, -u1, -v1, -1, u1 * v2, v1 * v2, v2])

    A = np.array(A)

    _, _, VT = np.linalg.svd(A)
    h = VT[-1]

    H = h.reshape(3, 3)
    return H

def filter_matches(matches, ratio_thres=0.7):
    filtered_matches = []
    for match in matches:
        good_match = []
        for m, n in match:
            if m.distance < ratio_thres * n.distance:
                good_match.append(m)
        filtered_matches.append(good_match)
        
    return filtered_matches


def find_homography(keypoints, filtered_matches):
    homographies = []
    skipped_indices = []  # Keep track of skipped images and their indices
    
    for i, matches in enumerate(filtered_matches):
        src_pts = np.float32([keypoints[0][m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
        dst_pts = np.float32([keypoints[i + 1][m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)
        
        H = ransac_homography(src_pts, dst_pts)
        
        if H is not None:
            H = H.astype(np.float32)
            homographies.append(H)
        else:
            print(f"Warning: Homography computation failed for image pair (0, {i + 1}). Skipping.")
            skipped_indices.append(i + 1) # Add indices of skipped images to the list
            continue
        
    return homographies, skipped_indices

def ransac_homography(src_pts, dst_pts, iterations=500, threshold=3):
    best_inlier_count = 0
    best_homography = None

    for _ in range(iterations):
        indices = np.random.choice(len(src_pts), 4, replace=True)
        src_subset = src_pts[indices].reshape(-1, 2)
        dst_subset = dst_pts[indices].reshape(-1, 2)

        homography = compute_homography_matrix(src_subset, dst_subset)
        if homography is None:
            continue

        inliers = 0
        for i in range(len(src_pts)):
            projected_point = np.dot(homography, np.append(src_pts[i], 1))
            projected_point = projected_point / projected_point[-1]
            distance = np.linalg.norm(projected_point - np.append(dst_pts[i], 1))

            if distance < threshold:
                inliers += 1

        if inliers > best_inlier_count:
            best_inlier_count = inliers
            best_homography = homography

    return best_homography

def read_ground_truth_homographies(dataset_path):
    H_files = sorted(glob.glob(os.path.join(dataset_path, "H_.txt")))
    ground_truth_homographies = []
    
    for filename in H_files:
        H = np.loadtxt(filename)
        ground_truth_homographies.append(H)
    
    return ground_truth_homographies


def warp_perspective(img, H, target_shape):
    h, w = target_shape
    target_x, target_y = np.meshgrid(np.arange(h), np.arange(w)) # Swap w and h
    target_coordinates = np.stack([target_y.ravel(), target_x.ravel(), np.ones(target_y.size)]) # Swap target_x and target_y

    source_coordinates = np.dot(np.linalg.inv(H), target_coordinates)
    source_coordinates /= source_coordinates[2, :]

    valid = np.logical_and(np.logical_and(0 <= source_coordinates[0, :], source_coordinates[0, :] < img.shape[0] - 1),
                            np.logical_and(0 <= source_coordinates[1, :], source_coordinates[1, :] < img.shape[1] - 1))

    valid_source_coordinates = np.round(source_coordinates[:, valid].astype(int)[:2]).astype(int)
    valid_target_coordinates = target_coordinates[:, valid].astype(int)[:2]

    valid_source_coordinates[0] = np.clip(valid_source_coordinates[0], 0, img.shape[0] - 1)
    valid_source_coordinates[1] = np.clip(valid_source_coordinates[1], 0, img.shape[1] - 1)
    valid_target_coordinates[0] = np.clip(valid_target_coordinates[0], 0, w - 1)
    valid_target_coordinates[1] = np.clip(valid_target_coordinates[1], 0, h - 1)

    warped_image = np.zeros((w, h, 3), dtype=np.uint8) # Swap w and h
    for i in range(3):
        warped_image[..., i][valid_target_coordinates[1], valid_target_coordinates[0]] = img[..., i][valid_source_coordinates[1], valid_source_coordinates[0]]

    return warped_image


def merge_images(sub_images, keypoints, filtered_matches, homographies, skipped_indices):
    ref_img = sub_images[0]

    for i in range(1, len(sub_images)):
        if i in skipped_indices:
            print(f"Image {i} was skipped due to homography computation failure.")
            continue

        img_i = sub_images[i]
        H_i = homographies[i - 1 - sum(idx < i for idx in skipped_indices)]

        min_x, min_y = 0, 0
        max_x, max_y = max(img_i.shape[1], ref_img.shape[1]), max(img_i.shape[0], ref_img.shape[0])
        trans_dst = [-min_x, -min_y]
        H_trans = np.array([[1.0, 0, trans_dst[0]], [0, 1.0, trans_dst[1]], [0, 0, 1.0]])

        result = warp_perspective(ref_img, H_trans, (max_y - min_y, max_x - min_x))  # Swap max_x and max_y
        img_warped = warp_perspective(img_i, H_i, result.shape[:2]) # Use result.shape[:2] for target_shape

        mask_result = (result > 0)
        mask_img_warped = (img_warped > 0)
        combined_mask = np.zeros_like(mask_result)

        min_height, min_width = min(result.shape[0], img_warped.shape[0]), min(result.shape[1], img_warped.shape[1])
        combined_mask[:min_height, :min_width] = mask_result[:min_height, :min_width] & mask_img_warped[:min_height, :min_width]

        nonzero_result = np.nonzero(combined_mask)
        result[nonzero_result] = img_warped[nonzero_result]

        ref_img = result
    return ref_img


def main(dataset_path):
    filenames = sorted(glob.glob(os.path.join(dataset_path, "*.png")))
    sub_images = []

    for filename in filenames:
        img = cv2.imread(filename, cv2.IMREAD_COLOR) # Load images as color
        sub_images.append(img)
        
    ground_truth_homographies = read_ground_truth_homographies(dataset_path)

    methods = ["SIFT", "SURF", "ORB"]
    for method in methods:
        start_time = time.time()
        
        keypoints, descriptors = feature_extraction(sub_images, method=method)
        matches = feature_matching(descriptors)
        filtered_matches = filter_matches(matches)
        homographies, skipped_indices = find_homography(keypoints, filtered_matches)
        panorama = merge_images(sub_images, keypoints, filtered_matches, homographies, skipped_indices)
        
        end_time = time.time()
        runtime = end_time - start_time

        print(f"Method: {method} - Runtime: {runtime:.2f} seconds")

        for idx, (image, kp) in enumerate(zip(sub_images, keypoints)):
            feature_plot = cv2.drawKeypoints(image, kp, None)
            plt.figure()
            plt.imshow(feature_plot, cmap="gray")
            plt.title(f"Feature Points - {method} - Image {idx}")

        for i, match in enumerate(filtered_matches):
            matching_plot = cv2.drawMatches(sub_images[0], keypoints[0], sub_images[i + 1], keypoints[i + 1], match, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
            plt.figure()
            plt.imshow(matching_plot, cmap="gray")
            plt.title(f"Feature Point Matching - {method} - Image 0 - Image {i + 1}")

        plt.figure()
        plt.imshow(panorama, cmap="gray", aspect="auto")
        plt.title(f"Panorama - {method}")

        print("\nGround truth homographies")
        for i, H_gt in enumerate(ground_truth_homographies):
            print(f"Image 0 to {i+1}:")
            print(H_gt)

        print("\nComputed homographies")
        for i, H_est in enumerate(homographies):
            print(f"Image 0 to {i+1}:")
            print(H_est)

    plt.show()


if __name__ == "__main__":
    dataset_path = "dataset/v_bird"
    main(dataset_path)"