ASCII_ART / app.py
RedBottle13's picture
add SII evaluation metric
55f1779
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()