nick-leland's picture
Merge branch 'main' of https://huggingface.co/spaces/nick-leland/ImageTransformationTool
53f436a
raw
history blame
9.58 kB
import numpy as np
import gradio as gr
from PIL import Image
from scipy import ndimage
import matplotlib.pyplot as plt
from bulk_bulge_generation import definitions, smooth
# from transformers import pipeline
import fastai
from fastcore.all import *
from fastai.vision.all import *
def apply_vector_field_transform(image, func, radius, center=(0.5, 0.5), strength=1, edge_smoothness=0.1, center_smoothness=0.20):
# 0.106 strength = .50
# 0.106 strength = 1
rows, cols = image.shape[:2]
max_dim = max(rows, cols)
#Normalize the positions
# Y Needs to be flipped
center_y = int(center[1] * rows)
center_x = int(center[0] * cols)
# Inverts the Y axis (Numpy is 0 index at top of image)
center_y = abs(rows - center_y)
print()
print(rows, cols)
print("y =", center_y, "/", rows)
print("x =", center_x, "/", cols)
print()
pixel_radius = int(max_dim * radius)
y, x = np.ogrid[:rows, :cols]
y = (y - center_y) / max_dim
x = (x - center_x) / max_dim
# Calculate distance from center
dist_from_center = np.sqrt(x**2 + y**2)
# Calculate function values
z = func(x, y)
# Calculate gradients
gy, gx = np.gradient(z)
# Creating a sigmoid function to apply to masks
def sigmoid(x, center, steepness):
return 1 / (1 + np.exp(-steepness * (x - center)))
print(radius)
print(strength)
print(edge_smoothness)
print(center_smoothness)
# Masking
edge_mask = np.clip((radius - dist_from_center) / (radius * edge_smoothness), 0, 1)
center_mask = np.clip((dist_from_center - radius * center_smoothness) / (radius * center_smoothness), 0, 1)
mask = edge_mask * center_mask
# Apply mask to gradients
gx = gx * mask
gy = gy * mask
# Normalize gradient vectors
magnitude = np.sqrt(gx**2 + gy**2)
magnitude[magnitude == 0] = 1 # Avoid division by zero
gx = gx / magnitude
gy = gy / magnitude
# Scale the effect (Play with the number 5)
scale_factor = strength * np.log(max_dim) / 100 # Adjust strength based on image size
gx = gx * scale_factor * mask
gy = gy * scale_factor * mask
# Create the mapping
x_new = x + gx
y_new = y + gy
# Convert back to pixel coordinates
x_new = x_new * max_dim + center_x
y_new = y_new * max_dim + center_y
# Ensure the new coordinates are within the image boundaries
x_new = np.clip(x_new, 0, cols - 1)
y_new = np.clip(y_new, 0, rows - 1)
# Apply the transformation to each channel
channels = [ndimage.map_coordinates(image[..., i], [y_new, x_new], order=1, mode='reflect')
for i in range(image.shape[2])]
transformed_image = np.dstack(channels).astype(image.dtype)
return transformed_image, (gx, gy)
def create_gradient_vector_field(gx, gy, image_shape, step=20, reverse=False):
"""
Create a gradient vector field visualization with option to reverse direction.
:param gx: X-component of the gradient
:param gy: Y-component of the gradient
:param image_shape: Shape of the original image (height, width)
:param step: Spacing between arrows
:param reverse: If True, reverse the direction of the arrows
:return: Gradient vector field as a numpy array (RGB image)
"""
rows, cols = image_shape
y, x = np.mgrid[step/2:rows:step, step/2:cols:step].reshape(2, -1).astype(int)
# Calculate the scale based on image size
max_dim = max(rows, cols)
scale = max_dim / 1000 # Adjusted for longer arrows
# Reverse direction if specified
direction = -1 if reverse else 1
fig, ax = plt.subplots(figsize=(cols/50, rows/50), dpi=100)
ax.quiver(x, y, direction * gx[y, x], direction * -gy[y, x],
scale=scale,
scale_units='width',
width=0.002 * max_dim / 500,
headwidth=8,
headlength=12,
headaxislength=0,
color='black',
minshaft=2,
minlength=0,
pivot='tail')
ax.set_xlim(0, cols)
ax.set_ylim(rows, 0)
ax.set_aspect('equal')
ax.axis('off')
fig.tight_layout(pad=0)
fig.canvas.draw()
vector_field = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
vector_field = vector_field.reshape(fig.canvas.get_width_height()[::-1] + (3,))
plt.close(fig)
return vector_field
#############################
# MAIN FUNCTION HERE
#############################
# pipeline = pipeline(task="image-classification", model="nick-leland/distortionml")
# Version Check
print(f"NumPy version: {np.__version__}")
print(f"PyTorch version: {torch.__version__}")
print(f"FastAI version: {fastai.__version__}")
learn_bias = load_learner('model_bias.pkl')
learn_fresh = load_learner('model_fresh.pkl')
def transform_image(image, func_choice, randomization_check, radius, center_x, center_y, strength, reverse_gradient=True, spiral_frequency=1):
I = np.asarray(Image.open(image))
def pinch(x, y):
return x**2 + y**2
def shift(x, y):
return np.arctan2(y, x)
def bulge(x, y):
r = -np.sqrt(x**2 + y**2)
return r
def spiral(x, y, frequency=1):
r = np.sqrt(x**2 + y**2)
theta = np.arctan2(y, x)
return r * np.sin(theta - frequency * r)
rng = np.random.default_rng()
if randomization_check == True:
radius, location, strength, edge_smoothness= definitions(rng)
center_x = location[0]
center_y = location[1]
# Temporarily disabling and using these values.
# edge_smoothness = 0.25 * strength
# center_smoothness = 0.25 * strength
edge_smoothness, center_smoothness = smooth(rng, strength)
if func_choice == "Pinch":
func = pinch
elif func_choice == "Spiral":
func = shift
elif func_choice == "Bulge":
func = bulge
edge_smoothness = 0
center_smoothness = 0
elif func_choice == "Volcano":
func = bulge
elif func_choice == "Shift Up":
func = lambda x, y: spiral(x, y, frequency=spiral_frequency)
transformed, (gx, gy) = apply_vector_field_transform(I, func, radius, (center_x, center_y), strength, edge_smoothness, center_smoothness)
vector_field = create_gradient_vector_field(gx, gy, I.shape[:2], reverse=reverse_gradient)
# GRADIO CHANGE HERE
# predictions = pipeline(transformed)
# Have to convert to image first
result = Image.fromarray(transformed)
categories = ['Distorted', 'Maze']
def clean_output(result_values):
pred, idx, probs = result_values[0], result_values[1], result_values[2]
return dict(zip(categories, map(float, probs)))
result_bias = learn_bias.predict(result)
result_fresh = learn_fresh.predict(result)
print("Results")
result_bias_final = clean_output(result_bias)
result_fresh_final = clean_output(result_fresh)
# return transformed, result_bias, result_fresh, vector_field
return transformed, result_bias_final, result_fresh_final, vector_field
demo = gr.Interface(
fn=transform_image,
inputs=[
gr.Image(type="filepath"),
gr.Dropdown(["Pinch", "Spiral", "Shift Up", "Bulge", "Volcano"], value="Bulge", label="Function"),
gr.Checkbox(label="Randomize inputs?"),
gr.Slider(0, 0.5, value=0.25, label="Radius (as fraction of image size)"),
gr.Slider(0, 1, value=0.5, label="Center X"),
gr.Slider(0, 1, value=0.5, label="Center Y"),
gr.Slider(0, 1, value=0.5, label="Strength"),
# gr.Slider(0, 1, value=0.5, label="Edge Smoothness"),
# gr.Slider(0, 0.5, value=0.1, label="Center Smoothness")
# gr.Checkbox(label="Reverse Gradient Direction"),
],
examples=[
[np.asarray(Image.open("examples/1500_maze.jpg")), "Bulge", True, 0.25, 0.5, 0.5, 0.5],
[np.asarray(Image.open("examples/2048_maze.jpg")), "Bulge", True, 0.25, 0.5, 0.5, 0.5],
[np.asarray(Image.open("examples/2300_fresh.jpg")), "Bulge", True, 0.25, 0.5, 0.5, 0.5],
[np.asarray(Image.open("examples/50_fresh.jpg")), "Bulge", True, 0.25, 0.5, 0.5, 0.5]
],
outputs=[
gr.Image(label="Transformed Image"),
# gr.Image(label="Result", num_top_classes=2)
gr.Label(),
gr.Label(),
gr.Image(label="Gradient Vector Field")
],
title="Image Transformation Demo!",
article="If you like this demo, please star the github repository for the project! Located [here!](https://github.com/nick-leland/DistortionML)",
description="This is the baseline function that will be used to generate the database for a machine learning model I am working on called 'DistortionMl'! The goal of this model is to detect and then reverse image transformations that can be generated here!\nYou can read more about the project at [this repository link](https://github.com/nick-leland/DistortionML). The main function that I was working on is the 'Bulge'/'Volcano' function, I can't really guarantee that the others work as well!\nI have just added the first baseline ML model to detect if a distortion has taken place! It was only trained on mazes though ([Dataset Here](https://www.kaggle.com/datasets/nickleland/distorted-mazes)) so in order for it to detect a distortion you have to use one of the images provided in the examples! Feel free to mess around wtih other images in the meantime though!"
)
demo.launch(share=True)