Spaces:
Runtime error
Runtime error
File size: 7,023 Bytes
83fd0ba 55f1779 83fd0ba 87951a7 83fd0ba c84a261 83fd0ba 55f1779 83fd0ba 87951a7 55f1779 87951a7 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 55f1779 83fd0ba 87951a7 83fd0ba 55f1779 83fd0ba 55f1779 87951a7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
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()
|