Remove redundancy in this code for the extract data thing:import pandas as pd
import math
import matplotlib.pyplot as plt
from mat4py import loadmat
import os

# Load the MATLAB .mat file
path = 'motioncapture_with_time.mat'
mat4py_data = loadmat(path)

# Identify all sheet names (assuming they start with 'S' followed by a number)
sheet_names = [key for key in mat4py_data.keys() if key.startswith('S')]

# Define markers to extract (simplified names without quotes and spaces)
markers = {
    'right_hip': ["XRxAsis", "YRxAsis", "ZRxAsis"],
    'right_knee': ["XRxLatCon", "YRxLatCon", "ZRxLatCon"],
    'right_ankle': ["XRxLatMal", "YRxLatMal", "ZRxLatMal"],
    'left_hip': ["XLxAsis", "YLxAsis", "ZLxAsis"],
    'left_knee': ["XLxLatCon", "YLxLatCon", "ZLxLatCon"],
    'left_ankle': ["XLxLatMal", "YLxLatMal", "ZLxLatMal"],
    'right_foot': ["XRxToe1", "YRxToe1", "ZRxToe1"],
    'left_foot': ["XLxToe1", "YLxToe1", "ZLxToe1"],
    'head': ["XRxCheec", "YRxCheec", "ZRxCheec"],
    'neck': ["XC7", "YC7", "ZC7"],
    'torso': ["Xmaxkif", "Ymaxkif", "Zmaxkif"],
}

# Create an output directory for the plots if it doesn't exist
output_dir = 'plots'
os.makedirs(output_dir, exist_ok=True)

# Define helper functions (consolidated redundant functions)
def calculate_distance(p1, p2):
    """Calculate Euclidean distance between two 3D points."""
    return math.sqrt(sum((a - b) ** 2 for a, b in zip(p1, p2)))

def calculate_angle(a, b, c, epsilon=1e-8):
    """
    Calculate the angle opposite side 'a' in a triangle with sides 'a', 'b', and 'c' using the cosine law.
    Returns 0.0 if either 'b' or 'c' is less than epsilon to avoid division by zero.
    """
    if b < epsilon or c < epsilon:
        return 0.0
    cos_theta = (b**2 + c**2 - a**2) / (2 * b * c)
    cos_theta = max(min(cos_theta, 1.0), -1.0)  # Clamp to [-1, 1] to avoid math domain error
    return math.degrees(math.acos(cos_theta))

def calculate_angle_between_points(p1, p2, p3):
    """Calculate the angle at point p2 formed by the line segments p2p1 and p2p3."""
    a = calculate_distance(p1, p2)
    b = calculate_distance(p2, p3)
    c = calculate_distance(p1, p3)
    return calculate_angle(a, b, c)

def calculate_walking_speed(start, end, final_time):
    """Calculate walking speed given start and end positions and the final time."""
    distance = calculate_distance(start, end)
    return distance / final_time if final_time != 0 else 0

def normalize_column_name(name):
    """Remove quotes and extra spaces from a column name."""
    return name.replace("'", "").strip()

# Initialize a list to store walking speeds
walking_speeds = []

# Process each sheet individually
for sheet_name in sheet_names:
    print(f"Processing {sheet_name}...")
    
    try:
        # Extract data from the current sheet
        sheet_data = mat4py_data[sheet_name]
        marker_names = sheet_data['marker_names']
        marker_data = sheet_data['marker_data']
        time_data = sheet_data['time']

        # Normalize column names
        normalized_marker_names = [normalize_column_name(name) for name in marker_names]
        
        # Create a dataframe for the current sheet with normalized column names
        df = pd.DataFrame(marker_data, columns=normalized_marker_names)
        time_series = pd.Series(time_data, name='Time')

        # Diagnostic: Print the actual column names after normalization
        print(f"Normalized Columns in {sheet_name}: {df.columns.tolist()}")

        # Extract marker positions
        extracted_data = {}
        for key, cols in markers.items():
            if all(col in df.columns for col in cols):
                extracted_data[key] = df[cols].astype(float).to_numpy()
            else:
                raise KeyError(f"One or more columns for '{key}' are missing.")

        # Calculate walking speed (using 'torso' marker)
        movement = extracted_data['torso']
        start = movement[0]
        end = movement[-1]
        final_time = time_series.iloc[-1]
        walking_speed = calculate_walking_speed(start, end, final_time)
        print(f"Walking speed for {sheet_name}: {walking_speed:.2f} meters per second")
        
        # Append walking speed to the list
        walking_speeds.append({'Sheet': sheet_name, 'Walking Speed (m/s)': walking_speed})

        # Define a list of tuples containing angle labels and corresponding marker keys
        angle_definitions = [
            ('Right Knee Angle', 'right_hip', 'right_knee', 'right_ankle'),
            ('Left Knee Angle', 'left_hip', 'left_knee', 'left_ankle'),
            ('Right Ankle Angle', 'right_knee', 'right_ankle', 'right_foot'),
            ('Left Ankle Angle', 'left_knee', 'left_ankle', 'left_foot'),
            ('Head/Neck Angle', 'head', 'neck', 'torso'),
        ]

        # Calculate angles over time
        angles = {}
        for label, point1, point2, point3 in angle_definitions:
            angles[label] = [
                calculate_angle_between_points(p1, p2, p3) 
                for p1, p2, p3 in zip(extracted_data[point1], extracted_data[point2], extracted_data[point3])
            ]

        # Plotting
        plt.figure(figsize=(14, 10))
        for label, angle_values in angles.items():
            plt.plot(time_series, angle_values, label=label, marker='o', markersize=4)
        
        # Add walking speed as a text annotation on the top-left corner of the plot
        plt.text(
            0.05, 0.95, f'Walking Speed: {walking_speed:.2f} m/s',
            horizontalalignment='left',
            verticalalignment='top',
            transform=plt.gca().transAxes,
            fontsize=12,
            bbox=dict(facecolor='white', alpha=0.5, edgecolor='none')
        )

        plt.title(f'Angle Profiles Over Time for {sheet_name}')
        plt.xlabel('Time (seconds)')
        plt.ylabel('Angle (degrees)')
        plt.legend()
        plt.grid(True)

        # Save the plot
        output_path = os.path.join(output_dir, f"{sheet_name}_output.png")
        plt.savefig(output_path)
        print(f"Plot saved to {output_path}\n")

        # Optionally display the plot
        # plt.show()

        # Clear the figure for the next iteration
        plt.close()

    except KeyError as e:
        print(f"Missing data in {sheet_name}: {e}. Skipping this sheet.\n")
        continue
    except Exception as e:
        print(f"An error occurred while processing {sheet_name}: {e}. Skipping this sheet.\n")
        continue

# After processing all sheets, save walking speeds to a CSV file
if walking_speeds:
    speeds_df = pd.DataFrame(walking_speeds)
    speeds_df.to_csv(os.path.join(output_dir, 'walking_speeds.csv'), index=False)
    print("Walking speeds saved to 'walking_speeds.csv' in the 'plots' directory.")

print("Processing complete for all sheets.")