now,  i have final_images folder contains 3 folder:
1. train images
2. validation images
3. test images
and train.csv , test.csv, validation.csv including tabular data.
i want to do feature extraction on images and feature normalization on tabular data (and any task you want) to build multimodal model for osteoprosis classification, i wil provide you all the code i write it and you provide me the rest of code:
import os
import matplotlib.pyplot as plt
from PIL import Image, ImageOps, ImageEnhance
import cv2
import numpy as np
import imgaug.augmenters as iaa
import shutil
from math import floor
import random
from IPython.display import Image as display_image
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, confusion_matrix
import tensorflow as tf
from tensorflow.keras.applications import EfficientNetB0
from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.efficientnet import preprocess_input
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
import random
import shutil
import cv2
from sklearn.model_selection import train_test_split
from albumentations import (
    Compose, HorizontalFlip, VerticalFlip, Rotate, ShiftScaleRotate,
    RandomBrightnessContrast, CLAHE, ElasticTransform, GridDistortion, OpticalDistortion
)
from albumentations.pytorch import ToTensorV2
from tqdm import tqdm
from google.colab import drive
drive.mount('/content/gdrive')

# Function to preprocess a single image
def preprocess_image(image_path, output_path):
    # Open the image
    image = Image.open(image_path)

    # Auto-orient the image based on its EXIF data
    image = ImageOps.exif_transpose(image)

    # Resize the image to 640x640
    image = image.resize((640, 640))

    # Convert the image to grayscale
    image = ImageOps.grayscale(image)

    # Auto adjust the contrast
    enhancer = ImageEnhance.Contrast(image)
    image = enhancer.enhance(2.0)  # You can adjust this value to change the contrast level

    # Save the processed image
    image.save(output_path)

# Path to the images folder
images_folder = '/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/images'
output_folder = '/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/processed_images'

# Ensure the output directory exists
os.makedirs(output_folder, exist_ok=True)

# Apply preprocessing to all images in the folder
for image_name in os.listdir(images_folder):
    image_path = os.path.join(images_folder, image_name)
    output_path = os.path.join(output_folder, image_name)
    preprocess_image(image_path, output_path)

print("All images have been successfully processed!")

# Select the first 5 images from each folder
original_image_names = os.listdir(images_folder)[:5]
processed_image_names = os.listdir(output_folder)[:5]

# Display original and processed images side by side
plt.figure(figsize=(15, 6))

# Display original images
for i, image_name in enumerate(original_image_names):
    image_path = os.path.join(images_folder, image_name)
    original_image = Image.open(image_path)

    plt.subplot(2, 5, i+1)
    plt.imshow(original_image)
    plt.title(f'Original {i+1}')
    plt.axis('off')

# Display processed images
for i, image_name in enumerate(processed_image_names):
    image_path = os.path.join(output_folder, image_name)
    processed_image = Image.open(image_path)

    plt.subplot(2, 5, i+6)
    plt.imshow(processed_image, cmap='gray')
    plt.title(f'Processed {i+1}')
    plt.axis('off')

plt.tight_layout()
plt.show()

def load_and_preprocess_tabular_data(csv_path):
    """
    Load and preprocess tabular data from a CSV file.
    """
    # Load the dataset using the appropriate engine
    df = pd.read_excel(csv_path, engine='openpyxl')

    # Data Cleaning
    # Remove any unwanted quotes from the 'Gender' column
    df['Gender'] = df['Gender'].astype(str).str.replace('"', '').astype(int)

    # Rename 'Diagosis' to 'Diagnosis'
    if 'Diagosis' in df.columns:
        df.rename(columns={'Diagosis': 'Diagnosis'}, inplace=True)

    # Define categorical columns
    categorical_cols = ['Gender', 'Smoker', 'Diabetic', 'Daily Eating habits',
                        'Hypothyroidism', 'History of Fracture',
                        'Family History of Osteoporosis']

    # Ensure categorical columns are integers
    for col in categorical_cols:
        df[col] = df[col].astype(int)

    # Save the cleaned data to a CSV file
    df.to_csv("/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/data.csv", index=False)
    print("Processing complete")

# Define the Excel file path
csv_path = '/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/data.xlsx'  # Replace with your path

# Load and preprocess tabular data
load_and_preprocess_tabular_data(csv_path)


# Set random seed for reproducibility
RANDOM_STATE = 42
np.random.seed(RANDOM_STATE)

# ===========================
# Configuration Parameters
# ===========================

# Paths
DATA_CSV_PATH = '/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/data.csv'               # Path to your tabular data CSV
IMAGES_DIR = '/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/processed_images'                    # Directory containing original images
AUGMENTED_IMAGES_DIR = '/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/augmented_images' # Directory to save augmented images
OUTPUT_DIR = '/content/gdrive/MyDrive/multimdal_osteoprosis_DATASET/final_images'            # Directory to save processed train, val, test data

# Split proportions
TEST_SIZE = 0.1
VAL_SIZE = 0.1  # Proportion of the training set to be used as validation
STRATIFY_COLUMNS = ['Diagnosis']         # Columns to stratify split

# Augmentation parameters
AUGMENTATIONS_PER_IMAGE = 5              # Number of augmentations per original image

# Numerical features to augment
NUMERICAL_FEATURES = ['Age', 'BMI']

# Output CSV filenames
TRAIN_CSV = 'train_data.csv'
VAL_CSV = 'val_data.csv'
TEST_CSV = 'test_data.csv'

# Albumentations augmentation pipeline
AUGMENTATION_PIPELINE = Compose([
    HorizontalFlip(p=0.5),
    VerticalFlip(p=0.5),
    Rotate(limit=20, p=0.7),
    ShiftScaleRotate(shift_limit=0.05, scale_limit=0.10, rotate_limit=15, p=0.7),
    RandomBrightnessContrast(p=0.7),
    CLAHE(p=0.5),
    ElasticTransform(p=0.5),
    GridDistortion(p=0.5),
    OpticalDistortion(p=0.5),
    # ToTensorV2(),  # Uncomment if using PyTorch tensors directly
], additional_targets={'image0': 'image'})

# Ensure output directories exist
os.makedirs(AUGMENTED_IMAGES_DIR, exist_ok=True)
os.makedirs(OUTPUT_DIR, exist_ok=True)


# ===========================
# Utility Functions
# ===========================

def load_data(csv_path):
    """
    Load the tabular data from a CSV file.
    """
    df = pd.read_csv(csv_path)
    required_columns = ['Gender', 'Age', 'Smoker', 'Diabetic',
                        'Daily Eating habits', 'Hypothyroidism',
                        'History of Fracture', 'Family History of Osteoporosis',
                        'BMI', 'Diagnosis', 'Image Features']
    for col in required_columns:
        if col not in df.columns:
            raise ValueError(f"Missing required column: {col}")
    return df

def split_data(df, test_size=TEST_SIZE, val_size=VAL_SIZE, stratify_cols=STRATIFY_COLUMNS):
    """
    Split the DataFrame into train, validation, and test sets with stratification.
    """
    stratify_vals = df[stratify_cols] if stratify_cols else None
    train_val_df, test_df = train_test_split(
        df,
        test_size=test_size,
        stratify=stratify_vals,
        random_state=RANDOM_STATE
    )
    stratify_vals_train_val = train_val_df[stratify_cols] if stratify_cols else None
    train_df, val_df = train_test_split(
        train_val_df,
        test_size=val_size / (1 - test_size),
        stratify=stratify_vals_train_val,
        random_state=RANDOM_STATE
    )
    return train_df.reset_index(drop=True), val_df.reset_index(drop=True), test_df.reset_index(drop=True)

def augment_image(image_path, augmentation_pipeline):
    """
    Apply augmentation pipeline to the image and return the augmented image.
    """
    image = cv2.imread(image_path)
    if image is None:
        raise ValueError(f"Image not found or unable to read: {image_path}")
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    augmented = augmentation_pipeline(image=image)
    return augmented['image']

def add_gaussian_noise(value, noise_std=0.02):
    """
    Add Gaussian noise to a numerical feature.
    """
    noise = np.random.normal(0, noise_std * value)
    return value + noise

def augment_tabular_row(row, numerical_features=NUMERICAL_FEATURES, noise_std=0.02):
    """
    Augment a single tabular row by adding Gaussian noise to numerical features.
    """
    augmented_row = row.copy()
    for feature in numerical_features:
        original_value = augmented_row[feature]
        augmented_value = add_gaussian_noise(original_value, noise_std)
        # Clip Age and BMI to realistic bounds
        if feature == 'Age':
            augmented_value = np.clip(augmented_value, 18, 100)
        elif feature == 'BMI':
            augmented_value = np.clip(augmented_value, 10, 50)
        augmented_row[feature] = round(augmented_value, 2)  # Round to two decimals
    return augmented_row

def save_image(image, save_path):
    """
    Save the image to the specified path.
    """
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
    cv2.imwrite(save_path, image)

def copy_original_images(df, src_dir, dest_dir):
    """
    Copy original images to the destination directory.
    """
    for idx, row in tqdm(df.iterrows(), total=df.shape[0], desc="Copying original images"):
        src_image_path = os.path.join(src_dir, row['Image Features'])
        dest_image_path = os.path.join(dest_dir, row['Image Features'])
        if not os.path.isfile(src_image_path):
            print(f"Warning: Image {src_image_path} does not exist. Skipping.")
            continue
        shutil.copy(src_image_path, dest_image_path)


# ===========================
# Main Processing Function
# ===========================

def process_data():
    """
    Main function to process the data: split, augment, and save.
    """
    # Step 1: Load Data
    print("Loading data...")
    df = load_data(DATA_CSV_PATH)
    print(f"Total samples: {len(df)}")

    # Step 2: Split Data
    print("Splitting data into train, validation, and test sets...")
    train_df, val_df, test_df = split_data(df)
    print(f"Train set size: {len(train_df)}")
    print(f"Validation set size: {len(val_df)}")
    print(f"Test set size: {len(test_df)}")

    # Step 3: Copy Original Test and Validation Images
    print("Copying original test and validation images...")
    test_images_dir = os.path.join(OUTPUT_DIR, 'test_images')
    val_images_dir = os.path.join(OUTPUT_DIR, 'val_images')
    os.makedirs(test_images_dir, exist_ok=True)
    os.makedirs(val_images_dir, exist_ok=True)
    copy_original_images(test_df, IMAGES_DIR, test_images_dir)
    copy_original_images(val_df, IMAGES_DIR, val_images_dir)

    # Step 4: Augment Training Data
    print("Augmenting training data...")
    augmented_train_data = []
    augmented_image_filenames = []

    # Create a new directory for augmented training images
    train_images_dir = os.path.join(OUTPUT_DIR, 'train_images')
    os.makedirs(train_images_dir, exist_ok=True)

    for idx, row in tqdm(train_df.iterrows(), total=train_df.shape[0], desc="Processing training samples"):
        original_image_filename = row['Image Features']
        original_image_path = os.path.join(IMAGES_DIR, original_image_filename)

        # Check if original image exists
        if not os.path.isfile(original_image_path):
            print(f"Warning: Image {original_image_path} does not exist. Skipping.")
            continue

        # Save the original image to the new train images directory
        dest_image_path = os.path.join(train_images_dir, original_image_filename)
        shutil.copy(original_image_path, dest_image_path)
        augmented_train_data.append(row.to_dict())

        # Perform augmentations
        for aug_num in range(1, AUGMENTATIONS_PER_IMAGE + 1):
            try:
                augmented_image = augment_image(original_image_path, AUGMENTATION_PIPELINE)
                augmented_image_filename = f"{os.path.splitext(original_image_filename)[0]}_aug{aug_num}{os.path.splitext(original_image_filename)[1]}"
                augmented_image_path = os.path.join(AUGMENTED_IMAGES_DIR, augmented_image_filename)
                save_image(augmented_image, augmented_image_path)
                
                # Augment tabular data
                augmented_row = augment_tabular_row(row)
                augmented_row['Image Features'] = augmented_image_filename
                augmented_train_data.append(augmented_row.to_dict())

            except Exception as e:
                print(f"Error augmenting image {original_image_filename}: {e}")
                continue

    # Step 5: Move Augmented Images to Train Images Directory and Update Filenames
    print("Organizing augmented images...")
    for filename in tqdm(os.listdir(AUGMENTED_IMAGES_DIR), desc="Moving augmented images"):
        src_path = os.path.join(AUGMENTED_IMAGES_DIR, filename)
        dest_path = os.path.join(train_images_dir, filename)
        shutil.move(src_path, dest_path)

    # Step 6: Create Augmented Training DataFrame
    augmented_train_df = pd.DataFrame(augmented_train_data)
    print(f"Augmented training set size: {len(augmented_train_df)}")

    # Step 7: Save Augmented and Split Data
    print("Saving processed data...")
    # Save training CSV
    train_csv_path = os.path.join(OUTPUT_DIR, TRAIN_CSV)
    augmented_train_df.to_csv(train_csv_path, index=False)
    print(f"Training data saved to {train_csv_path}")

    # Save validation CSV
    val_csv_path = os.path.join(OUTPUT_DIR, VAL_CSV)
    val_df.to_csv(val_csv_path, index=False)
    print(f"Validation data saved to {val_csv_path}")

    # Save test CSV
    test_csv_path = os.path.join(OUTPUT_DIR, TEST_CSV)
    test_df.to_csv(test_csv_path, index=False)
    print(f"Test data saved to {test_csv_path}")

    print("Data processing and augmentation completed successfully.")

# ===========================
# Execute the Script
# ===========================

process_data()