Spaces:
Runtime error
Runtime error
from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageFont | |
import numpy as np | |
import math | |
import gradio as gr | |
from skimage.metrics import structural_similarity as ssim | |
import cv2 | |
##### CONSTANTS ##### | |
# ASCII characters used to represent image pixels, reversed for better contrast in mapping | |
CHARS = ' .:-=+*#%@'[::-1] | |
# Convert the characters to a list for easier access | |
CHAR_ARRAY = list(CHARS) | |
# Number of available ASCII characters | |
CHAR_LEN = len(CHAR_ARRAY) | |
# Grayscale level for each ASCII character, determining how many shades of gray each character represents | |
GRAYSCALE_LEVEL = CHAR_LEN / 256 | |
# Target number of characters per row | |
TARGET_WIDTH = 200 | |
# Character dimensions (width and height in pixels) used to match image aspect ratio to character aspect ratio | |
CHAR_W = 6 | |
CHAR_H = 14 | |
##### ASCII ART GENERATION ##### | |
def getChar(inputInt, gamma=1.8): | |
"""Map a grayscale pixel value to an ASCII character with gamma correction applied.""" | |
# Adjust the input pixel intensity using gamma correction for perceptual brightness adjustment | |
inputInt = (inputInt / 255) ** gamma * 255 | |
# Map the corrected pixel value to an appropriate ASCII character | |
return CHAR_ARRAY[math.floor(inputInt * GRAYSCALE_LEVEL)] | |
def load_and_preprocess_image(image): | |
"""Resize and preprocess the input image, adjusting contrast and blurring for better ASCII conversion.""" | |
width, height = image.size | |
# Calculate the scaling factor to match the target character width | |
scale_factor = TARGET_WIDTH / width | |
# Resize the image, maintaining the aspect ratio considering the character dimensions | |
new_width = TARGET_WIDTH | |
new_height = int(scale_factor * height * (CHAR_W / CHAR_H)) # Adjust height to maintain aspect ratio | |
# Resize the image based on the calculated dimensions | |
im = image.resize((new_width, new_height)) | |
# Enhance contrast to bring out more detail in the ASCII representation | |
im = ImageOps.equalize(im, mask=None) | |
# Apply a slight blur to reduce noise and simplify pixel values | |
im = im.filter(ImageFilter.GaussianBlur(radius=0.5)) | |
return im | |
def create_ascii_art(im): | |
"""Convert a preprocessed image into ASCII art by mapping grayscale values to ASCII characters.""" | |
# Convert the image to grayscale | |
grayscale_image = im.convert("L") | |
# Get the pixel values as a NumPy array for easier processing | |
pix = np.array(grayscale_image) | |
width, height = grayscale_image.size | |
# Create a string that holds the ASCII art, where each character represents a pixel | |
ascii_art = "" | |
for i in range(height): | |
for j in range(width): | |
# Append the corresponding ASCII character for each pixel | |
ascii_art += getChar(pix[i, j]) | |
# Newline after each row to maintain image structure | |
ascii_art += '\n' | |
return ascii_art | |
def draw_ascii_image(ascii_art_string, char_width, char_height, font_size): | |
"""Draw the ASCII art string onto an image.""" | |
# Split the ASCII art string into lines | |
lines = ascii_art_string.split('\n') | |
# Determine the dimensions of the image based on the number of characters | |
width = max(len(line) for line in lines) | |
height = len(lines) | |
# Create a blank white image based on the ASCII art size and font size | |
img_width = width * char_width | |
img_height = height * char_height | |
ascii_image = Image.new("RGB", (img_width, img_height), "white") | |
draw = ImageDraw.Draw(ascii_image) | |
# Use default or custom font (Pillow's default font in this case) | |
font = ImageFont.load_default() | |
# Draw the ASCII art on the image | |
for i, line in enumerate(lines): | |
# Draw each line of the ASCII art onto the image | |
draw.text((0, i * char_height), line, font=font, fill="black") | |
return ascii_image | |
##### EVALUATION ##### | |
def ascii_to_image(ascii_art, width, height): | |
"""Convert ASCII art back to an image by mapping characters to grayscale values.""" | |
char_to_gray = {c: int((i / CHAR_LEN) * 255) for i, c in enumerate(CHAR_ARRAY)} | |
ascii_lines = ascii_art.split('\n') | |
# Create an empty numpy array to store the pixel values | |
ascii_image = np.zeros((height, width), dtype=np.uint8) | |
for i, line in enumerate(ascii_lines): | |
if i >= height: | |
break | |
for j, char in enumerate(line): | |
if j >= width: | |
break | |
ascii_image[i, j] = char_to_gray.get(char, 255) # Default to white if character not in mapping | |
return Image.fromarray(ascii_image) | |
def calculate_ssi(original_image, ascii_art_image): | |
"""Calculate Structural Similarity Index (SSI) between the original and reconstructed images.""" | |
original_image = original_image.convert("L") | |
# Resize the original image to match the dimensions of the ASCII art image | |
original_image_resized = original_image.resize(ascii_art_image.size) | |
# Convert both images to NumPy arrays | |
original_array = np.array(original_image_resized) | |
ascii_array = np.array(ascii_art_image) | |
# Calculate SSI | |
ssi_value, _ = ssim(original_array, ascii_array, full=True) | |
return ssi_value | |
##### MAIN FUNCTION FOR GRADIO INTERFACE ##### | |
def process_image(image): | |
"""Process the input image to generate both an ASCII art image and a downloadable text file.""" | |
resized_image = load_and_preprocess_image(image) | |
ascii_art = create_ascii_art(resized_image) | |
output_image = draw_ascii_image(ascii_art, char_width=CHAR_W, char_height=CHAR_H, font_size=10) | |
# Convert the ASCII art back into an image for SSI comparison | |
ascii_art_image = ascii_to_image(ascii_art, resized_image.width, resized_image.height) | |
# Calculate SSI between the original and ASCII art image | |
ssi_value = calculate_ssi(image, ascii_art_image) | |
# Save the ASCII art as a text file | |
ascii_txt_path = "ascii_art.txt" | |
with open(ascii_txt_path, "w") as text_file: | |
text_file.write(ascii_art) | |
print(f"Structural Similarity Index (SSI): {ssi_value}") | |
return output_image, ascii_txt_path, ssi_value | |
##### GRADIO INTERFACE ##### | |
def gradio_interface(image): | |
ascii_image, txt_file, ssi_value = process_image(image) | |
return ascii_image, txt_file, ssi_value | |
demo = gr.Interface( | |
fn=gradio_interface, | |
inputs=gr.Image(type="pil", label="Upload an Image", height=300), | |
outputs=[ | |
gr.Image(type="pil", label="ASCII Art Image", height=300), | |
gr.File(label="Download ASCII Art Text File", height=50), | |
gr.Textbox(label="SSI Value") | |
], | |
title="ASCII Art Generator with SSI Metric", | |
description="Upload an image, generate ASCII art, and calculate the Structural Similarity Index (SSI).", | |
allow_flagging="never", | |
examples=[ | |
['images/building.jpg'], | |
['images/cat.webp'], | |
['images/dog.jpg'], | |
['images/people.jpg'], | |
['images/cartoon.png'], | |
['images/einstein.jpg'], | |
], | |
) | |
demo.launch() | |