two drawback 
1) image coming back 
C:\Users\heman\AppData\Local\Programs\Python\Python312\Lib\site-packages\torchvision\io\image.py:14: UserWarning: Failed to load image Python extension: '[WinError 127] The specified procedure could not be found'If you don't plan on using image functionality from `torchvision.io`, you can ignore this warning. Otherwise, there might be something wrong with your environment. Did you have `libjpeg` or `libpng` installed before building `torchvision` from source?
  warn(
Image Tensor Shape (C, H, W): torch.Size([3, 256, 256])
Batch Shape: torch.Size([2, 3, 256, 256])
Applied: Anisotropic Diffusion
Failed to apply Autoregressive Prediction: autoregressive_patch_prediction() got an unexpected keyword argument 'tensor'
Applied: Covariance Whitening
Applied: Cross-Domain Permutation
Applied: Entropy-Based Patch Swap
Applied: Directional Gradient Swap
Failed to apply Fourier Patch Mixing: fourier_patch_mixing() got an unexpected keyword argument 'tensor'
c:\Users\heman\Desktop\Coding\tensor_manipulation\image_manipulation\frequencyaware.py:49: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).
  x = torch.fft.ifft(torch.tensor(X_sym), dim=-1).real[..., :N]
Applied: Frequency Aware Scaling
Failed to apply Frequency Selective Masking: frequency_selective_patch_masking() got an unexpected keyword argument 'tensor'
Failed to apply Gabor Filter Augmentation: gabor_filter_patch_augmentation() got an unexpected keyword argument 'tensor'
Applied: Kurtosis-Based Selection
Applied: Laplacian Pyramid Blending
Failed to apply LBP Patch Encoding: apply_lbp_patch_encoding() got an unexpected keyword argument 'tensor'
Applied: MRF Patch Stitching
Failed to apply Add Gaussian Noise: add_random_gaussian_noise_to_patches() got an unexpected keyword argument 'tensor'
Applied: Stochastic Patch Replacement
Failed to apply Temporal Patch Mixing: temporal_patch_mixing() got an unexpected keyword argument 'tensor'
Failed to apply Texture Patch Reorganization: texture_based_patch_reorganization() got an unexpected keyword argument 'tensor'
Applied: Wavelet Patch Fusion
import torch
from image_manipulation import (
    anisotropic_diffusion_patch_filtering,
    autoregressive_patch_prediction,
    covariance_patch_whitening,
    cross_domain_random_permutation,
    entropy_based_patch_swap,
    directional_patch_gradient_swapping,
    fourier_patch_mixing,
    frequency_aware_patch_scaling,
    frequency_selective_patch_masking,
    gabor_filter_patch_augmentation,
    kurtosis_based_patch_selection,
    laplacian_pyramid_blending,
    apply_lbp_patch_encoding,
    mrf_patch_stitching,
    add_random_gaussian_noise_to_patches,
    stochastic_patch_replacement,
    temporal_patch_mixing,
    texture_based_patch_reorganization,
    wavelet_patch_fusion
)

from PIL import Image
import numpy as np
from torchvision import transforms
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# Function to convert a tensor to a NumPy array suitable for Plotly
def tensor_to_image(tensor):
    """
    Convert a torch tensor to a NumPy array for Plotly.
    Assumes tensor shape is (C, H, W) and values are in [0, 1].
    """
    tensor = tensor.detach().cpu()
    if tensor.ndimension() == 4:
        tensor = tensor[0]  # Take the first image in the batch
    tensor = tensor.clamp(0, 1)  # Ensure values are within [0,1]
    np_image = tensor.permute(1, 2, 0).numpy()  # (H, W, C)
    return np_image

# Load and preprocess the image
file_img = r"C:\Users\heman\Desktop\Coding\buliding.jpg"
load_image = Image.open(file_img).convert("RGB")  # Ensure image is in RGB
transform = transforms.Compose([
    transforms.Resize((256, 256)), 
    transforms.ToTensor(),         # Convert the image to a tensor and normalize to [0, 1]
])
image_tensor = transform(load_image)

# Create a batch with two identical images
batch = torch.stack([image_tensor, image_tensor])  
print(f"Image Tensor Shape (C, H, W): {image_tensor.shape}") 
print(f"Batch Shape: {batch.shape}")
# Dictionary to hold the function names and their corresponding functions
manipulation_functions = {
    "Anisotropic Diffusion": anisotropic_diffusion_patch_filtering,
    "Autoregressive Prediction": autoregressive_patch_prediction,
    "Covariance Whitening": covariance_patch_whitening,
    "Cross-Domain Permutation": cross_domain_random_permutation,
    "Entropy-Based Patch Swap": entropy_based_patch_swap,
    "Directional Gradient Swap": directional_patch_gradient_swapping,
    "Fourier Patch Mixing": fourier_patch_mixing,
    "Frequency Aware Scaling": frequency_aware_patch_scaling,
    "Frequency Selective Masking": frequency_selective_patch_masking,
    "Gabor Filter Augmentation": gabor_filter_patch_augmentation,
    "Kurtosis-Based Selection": kurtosis_based_patch_selection,
    "Laplacian Pyramid Blending": laplacian_pyramid_blending,
    "LBP Patch Encoding": apply_lbp_patch_encoding,
    "MRF Patch Stitching": mrf_patch_stitching,
    "Add Gaussian Noise": add_random_gaussian_noise_to_patches,
    "Stochastic Patch Replacement": stochastic_patch_replacement,
    "Temporal Patch Mixing": temporal_patch_mixing,
    "Texture Patch Reorganization": texture_based_patch_reorganization,
    "Wavelet Patch Fusion": wavelet_patch_fusion
}

# Apply all manipulation functions
processed_tensors = {}
for name, func in manipulation_functions.items():
    try:
        # Assuming all functions take 'tensor' and 'patch_size' as arguments
        # Adjust arguments as per your actual API
        processed = func(tensor=batch, patch_size=(16, 16))
        processed_tensors[name] = processed
        print(f"Applied: {name}")
    except Exception as e:
        print(f"Failed to apply {name}: {e}")

# Prepare images for plotting
images = {}

# Original Images
images["Original Image 1"] = tensor_to_image(batch[0])
images["Original Image 2"] = tensor_to_image(batch[1])

# Processed Images
for name, tensor in processed_tensors.items():
    images[name + " (Image 1)"] = tensor_to_image(tensor[0])
    images[name + " (Image 2)"] = tensor_to_image(tensor[1])

# Determine the number of images to plot
num_images = len(images)
cols = 3  # Number of columns in the plot grid
rows = num_images // cols + int(num_images % cols != 0)

# Create subplots
fig = make_subplots(
    rows=rows,
    cols=cols,
    subplot_titles=list(images.keys()),
    horizontal_spacing=0.02,
    vertical_spacing=0.05
)

# Add images to the subplots
for idx, (title, img) in enumerate(images.items()):
    row = idx // cols + 1
    col = idx % cols + 1
    fig.add_trace(
        go.Image(z=img),
        row=row,
        col=col
    )

# Update layout
fig.update_layout(
    height=300 * rows,  # Adjust height based on number of rows
    width=300 * cols,   # Adjust width based on number of columns
    title_text="Original and Manipulated Images",
    showlegend=False
)

# Hide axis lines and labels for all subplots
fig.update_xaxes(showticklabels=False).update_yaxes(showticklabels=False)

# Display the plot
fig.show()