Spaces:
Running
Running
first commit
Browse files- app.py +163 -0
- controlnet.py +67 -0
- game_of_life.py +97 -0
- requirements.txt +7 -0
- sky-gol-image.jpeg +0 -0
- utils.py +48 -0
app.py
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image
|
2 |
+
import numpy as np
|
3 |
+
import gradio as gr
|
4 |
+
import spaces
|
5 |
+
import torch
|
6 |
+
from tqdm import tqdm
|
7 |
+
|
8 |
+
from controlnet import QRControlNet
|
9 |
+
from game_of_life import GameOfLife
|
10 |
+
from utils import resize_image, generate_image_from_grid
|
11 |
+
|
12 |
+
|
13 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
14 |
+
controlnet = QRControlNet(device=device)
|
15 |
+
|
16 |
+
|
17 |
+
def generate_all_images(
|
18 |
+
gol_grids: list[np.array],
|
19 |
+
source_image: Image,
|
20 |
+
num_inference_steps: int,
|
21 |
+
controlnet_conditioning_scale: float,
|
22 |
+
strength: float,
|
23 |
+
prompt: str,
|
24 |
+
negative_prompt: str,
|
25 |
+
seed: int,
|
26 |
+
guidance_scale: float,
|
27 |
+
img_size: int,
|
28 |
+
):
|
29 |
+
|
30 |
+
controlnet_conditioning_scale = float(controlnet_conditioning_scale)
|
31 |
+
source_image = resize_image(source_image, resolution=img_size)
|
32 |
+
images = []
|
33 |
+
for grid in tqdm(gol_grids):
|
34 |
+
|
35 |
+
grid_inverse = 1 - grid # invert the grid for controlnet
|
36 |
+
grid_inverse_image = generate_image_from_grid(grid_inverse, img_size=img_size)
|
37 |
+
|
38 |
+
image = controlnet.generate_image(
|
39 |
+
source_image=source_image,
|
40 |
+
control_image=grid_inverse_image,
|
41 |
+
num_inference_steps=num_inference_steps,
|
42 |
+
controlnet_conditioning_scale=controlnet_conditioning_scale,
|
43 |
+
strength=strength,
|
44 |
+
prompt=prompt,
|
45 |
+
negative_prompt=negative_prompt,
|
46 |
+
seed=seed,
|
47 |
+
guidance_scale=guidance_scale,
|
48 |
+
img_size=img_size,
|
49 |
+
)
|
50 |
+
images.append(image)
|
51 |
+
|
52 |
+
return images
|
53 |
+
|
54 |
+
|
55 |
+
def make_gif(images: list[Image.Image], gif_path):
|
56 |
+
images[0].save(
|
57 |
+
gif_path,
|
58 |
+
save_all=True,
|
59 |
+
append_images=images[1:],
|
60 |
+
duration=200, # Duration between frames in milliseconds
|
61 |
+
loop=0,
|
62 |
+
) # Loop forever
|
63 |
+
return gif_path
|
64 |
+
|
65 |
+
|
66 |
+
@spaces.GPU
|
67 |
+
def generate(
|
68 |
+
source_image,
|
69 |
+
prompt,
|
70 |
+
negative_prompt,
|
71 |
+
seed,
|
72 |
+
num_inference_steps,
|
73 |
+
num_gol_steps,
|
74 |
+
gol_grid_dim,
|
75 |
+
img_size,
|
76 |
+
controlnet_conditioning_scale,
|
77 |
+
strength,
|
78 |
+
guidance_scale,
|
79 |
+
):
|
80 |
+
|
81 |
+
# Compute the Game of Life first
|
82 |
+
gol = GameOfLife()
|
83 |
+
gol.set_random_state(dim=(gol_grid_dim, gol_grid_dim), p=0.5, seed=seed)
|
84 |
+
gol.generate_n_steps(n=num_gol_steps)
|
85 |
+
|
86 |
+
gol_grids = gol.game_history
|
87 |
+
|
88 |
+
# Generate the gif for the original Game of Life
|
89 |
+
gol_images = [
|
90 |
+
generate_image_from_grid(grid, img_size=img_size) for grid in gol_grids
|
91 |
+
]
|
92 |
+
path_gol_gif = make_gif(gol_images, "gol_original.gif")
|
93 |
+
|
94 |
+
# Generate the gif for the ControlNet Game of Life
|
95 |
+
controlnet_images = generate_all_images(
|
96 |
+
gol_grids=gol_grids,
|
97 |
+
source_image=source_image,
|
98 |
+
num_inference_steps=num_inference_steps,
|
99 |
+
controlnet_conditioning_scale=controlnet_conditioning_scale,
|
100 |
+
strength=strength,
|
101 |
+
prompt=prompt,
|
102 |
+
negative_prompt=negative_prompt,
|
103 |
+
seed=seed,
|
104 |
+
guidance_scale=guidance_scale,
|
105 |
+
img_size=img_size,
|
106 |
+
)
|
107 |
+
|
108 |
+
path_gol_controlnet = make_gif(controlnet_images, "gol_controlnet.gif")
|
109 |
+
|
110 |
+
return path_gol_controlnet, path_gol_gif
|
111 |
+
|
112 |
+
|
113 |
+
source_image = gr.Image(label="Source Image", type="pil", value="sky-gol-image.jpeg")
|
114 |
+
|
115 |
+
output_controlnet = gr.Image(label="ControlNet Game of Life")
|
116 |
+
output_gol = gr.Image(label="Original Game of Life")
|
117 |
+
prompt = gr.Textbox(
|
118 |
+
label="Prompt", value="clear sky with clouds, high quality, background 4k"
|
119 |
+
)
|
120 |
+
negative_prompt = gr.Textbox(
|
121 |
+
label="Negative Prompt",
|
122 |
+
value="ugly, disfigured, low quality, blurry, nsfw, qr code",
|
123 |
+
)
|
124 |
+
seed = gr.Number(label="Seed", value=42)
|
125 |
+
num_inference_steps = gr.Number(label="Controlnet Inference Steps", value=50)
|
126 |
+
num_gol_steps = gr.Slider(
|
127 |
+
label="Number of Game of Life Steps",
|
128 |
+
minimum=2,
|
129 |
+
maximum=100,
|
130 |
+
step=1,
|
131 |
+
value=40,
|
132 |
+
)
|
133 |
+
gol_grid_dim = gr.Number(
|
134 |
+
label="Game of Life Grid Dimension",
|
135 |
+
value=10,
|
136 |
+
)
|
137 |
+
|
138 |
+
img_size = gr.Number(label="Image Size (pixels)", value=512)
|
139 |
+
controlnet_conditioning_scale = gr.Slider(
|
140 |
+
label="Controlnet Conditioning Scale", minimum=0.1, maximum=10.0, value=2.0
|
141 |
+
)
|
142 |
+
strength = gr.Slider(label="Strength", minimum=0.1, maximum=1.0, value=0.9)
|
143 |
+
guidance_scale = gr.Slider(label="Guidance Scale", minimum=1, maximum=100, value=20)
|
144 |
+
|
145 |
+
|
146 |
+
demo = gr.Interface(
|
147 |
+
fn=generate,
|
148 |
+
inputs=[
|
149 |
+
source_image,
|
150 |
+
prompt,
|
151 |
+
negative_prompt,
|
152 |
+
seed,
|
153 |
+
num_inference_steps,
|
154 |
+
num_gol_steps,
|
155 |
+
gol_grid_dim,
|
156 |
+
img_size,
|
157 |
+
controlnet_conditioning_scale,
|
158 |
+
strength,
|
159 |
+
guidance_scale,
|
160 |
+
],
|
161 |
+
outputs=[output_controlnet, output_gol],
|
162 |
+
)
|
163 |
+
demo.launch()
|
controlnet.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from diffusers import (
|
3 |
+
StableDiffusionControlNetImg2ImgPipeline,
|
4 |
+
ControlNetModel,
|
5 |
+
DDIMScheduler,
|
6 |
+
)
|
7 |
+
|
8 |
+
from PIL import Image
|
9 |
+
|
10 |
+
|
11 |
+
class QRControlNet:
|
12 |
+
|
13 |
+
def __init__(self, device: str = "cuda"):
|
14 |
+
|
15 |
+
torch_dtype = torch.float16 if device == "cuda" else torch.float32
|
16 |
+
controlnet = ControlNetModel.from_pretrained(
|
17 |
+
"DionTimmer/controlnet_qrcode-control_v1p_sd15", torch_dtype=torch_dtype
|
18 |
+
)
|
19 |
+
|
20 |
+
pipe = StableDiffusionControlNetImg2ImgPipeline.from_pretrained(
|
21 |
+
"runwayml/stable-diffusion-v1-5",
|
22 |
+
controlnet=controlnet,
|
23 |
+
safety_checker=None,
|
24 |
+
torch_dtype=torch_dtype,
|
25 |
+
).to(device)
|
26 |
+
|
27 |
+
if device == "cuda":
|
28 |
+
pipe.enable_xformers_memory_efficient_attention()
|
29 |
+
pipe.enable_model_cpu_offload()
|
30 |
+
|
31 |
+
pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config)
|
32 |
+
self.pipe = pipe
|
33 |
+
|
34 |
+
def generate_image(
|
35 |
+
self,
|
36 |
+
source_image: Image,
|
37 |
+
control_image: Image,
|
38 |
+
prompt: str,
|
39 |
+
negative_prompt: str,
|
40 |
+
img_size=512,
|
41 |
+
num_inference_steps: int = 50,
|
42 |
+
guidance_scale: int = 20,
|
43 |
+
controlnet_conditioning_scale: float = 3.0,
|
44 |
+
strength=0.9,
|
45 |
+
seed=42,
|
46 |
+
**kwargs
|
47 |
+
):
|
48 |
+
|
49 |
+
width = height = img_size
|
50 |
+
generator = torch.manual_seed(seed)
|
51 |
+
|
52 |
+
image = self.pipe(
|
53 |
+
prompt=prompt,
|
54 |
+
negative_prompt=negative_prompt,
|
55 |
+
image=source_image,
|
56 |
+
control_image=control_image,
|
57 |
+
width=width,
|
58 |
+
height=height,
|
59 |
+
guidance_scale=guidance_scale,
|
60 |
+
controlnet_conditioning_scale=controlnet_conditioning_scale, # 3.0,
|
61 |
+
generator=generator,
|
62 |
+
strength=strength,
|
63 |
+
num_inference_steps=num_inference_steps,
|
64 |
+
**kwargs
|
65 |
+
)
|
66 |
+
|
67 |
+
return image.images[0]
|
game_of_life.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from scipy import signal
|
3 |
+
from PIL import Image
|
4 |
+
|
5 |
+
from utils import generate_image_from_grid
|
6 |
+
|
7 |
+
|
8 |
+
class GameOfLife:
|
9 |
+
|
10 |
+
def __init__(self):
|
11 |
+
"""Initialize the game.
|
12 |
+
dim: dimensions of the board
|
13 |
+
p: probability of a cell being dead at init
|
14 |
+
seed: (optional) for reproducibility, set to None for a new random state
|
15 |
+
init_state: (optional) a np.array grid to start the game with
|
16 |
+
"""
|
17 |
+
self.kernel = [
|
18 |
+
[1, 1, 1],
|
19 |
+
[1, 0, 1],
|
20 |
+
[1, 1, 1],
|
21 |
+
]
|
22 |
+
|
23 |
+
self.kernel = np.ones((3, 3))
|
24 |
+
self.kernel[1, 1] = 0
|
25 |
+
self.game_history = []
|
26 |
+
self.state = None
|
27 |
+
self.step_counter = 0
|
28 |
+
|
29 |
+
def set_random_state(self, dim=(100, 100), p=0.5, seed=None):
|
30 |
+
if seed:
|
31 |
+
np.random.seed(seed)
|
32 |
+
self.state = (np.random.random(dim) < p).astype("int")
|
33 |
+
self.game_history.append(self.state.copy())
|
34 |
+
|
35 |
+
def set_empty_state(self, dim=(100, 100)):
|
36 |
+
self.state = np.zeros(dim)
|
37 |
+
self.game_history.append(self.state.copy())
|
38 |
+
|
39 |
+
def set_state_from_array(self, array):
|
40 |
+
self.state = array.copy()
|
41 |
+
self.game_history.append(self.state.copy())
|
42 |
+
|
43 |
+
def count_neighbors(self):
|
44 |
+
"""
|
45 |
+
Count the number of live neighbors each cell in self.state has with convolutions.
|
46 |
+
"""
|
47 |
+
self.neighbors = signal.convolve2d(
|
48 |
+
self.state, self.kernel, boundary="fill", fillvalue=0, mode="same"
|
49 |
+
).astype("int")
|
50 |
+
|
51 |
+
def place_blob(self, blob, i, j):
|
52 |
+
"""Place a blob at coordinates i,j
|
53 |
+
blob: ndarray of zeros and ones
|
54 |
+
i: int
|
55 |
+
j: int
|
56 |
+
"""
|
57 |
+
try:
|
58 |
+
self.state[i : i + blob.shape[0], j : j + blob.shape[1]] = blob
|
59 |
+
except:
|
60 |
+
print("Check bounds of box vs size of game!")
|
61 |
+
|
62 |
+
def step(self):
|
63 |
+
"""Update the game based on conway game of life rules"""
|
64 |
+
# Count the number of neighbors via convolution
|
65 |
+
self.count_neighbors()
|
66 |
+
|
67 |
+
# Copy of initial state
|
68 |
+
self.new_state = self.state
|
69 |
+
|
70 |
+
# Rebirth if cell is dead and has three live neighbors
|
71 |
+
self.new_state += np.logical_and(self.neighbors == 3, self.state == 0)
|
72 |
+
|
73 |
+
# Death if cell has less than 2 neighbors
|
74 |
+
self.new_state -= np.logical_and(self.neighbors < 2, self.state == 1)
|
75 |
+
|
76 |
+
# Death if cell has more than 3 neighbors
|
77 |
+
self.new_state -= np.logical_and(self.neighbors > 3, self.state == 1)
|
78 |
+
|
79 |
+
# Update game state
|
80 |
+
self.state = self.new_state
|
81 |
+
|
82 |
+
# Save state to history
|
83 |
+
self.game_history.append(self.state.copy())
|
84 |
+
|
85 |
+
# Update step counter
|
86 |
+
self.step_counter += 1
|
87 |
+
|
88 |
+
def generate_n_steps(self, n):
|
89 |
+
for _ in range(n):
|
90 |
+
self.step()
|
91 |
+
|
92 |
+
if np.array_equal(self.game_history[-1], self.game_history[-2]):
|
93 |
+
# If the game is stable, break
|
94 |
+
break
|
95 |
+
|
96 |
+
def generate_image(self, grid: np.array, img_size: int = 512) -> Image:
|
97 |
+
return generate_image_from_grid(grid, img_size)
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
torch
|
2 |
+
diffusers
|
3 |
+
pillow
|
4 |
+
transformers
|
5 |
+
accelerate
|
6 |
+
xformers
|
7 |
+
numpy
|
sky-gol-image.jpeg
ADDED
utils.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image
|
2 |
+
import numpy as np
|
3 |
+
import requests
|
4 |
+
from io import BytesIO
|
5 |
+
|
6 |
+
|
7 |
+
def resize_image(input_image: Image, resolution: int):
|
8 |
+
input_image = input_image.convert("RGB")
|
9 |
+
W, H = input_image.size
|
10 |
+
k = float(resolution) / min(H, W)
|
11 |
+
H *= k
|
12 |
+
W *= k
|
13 |
+
H = int(round(H / 64.0)) * 64
|
14 |
+
W = int(round(W / 64.0)) * 64
|
15 |
+
img = input_image.resize((W, H), resample=Image.LANCZOS)
|
16 |
+
return img
|
17 |
+
|
18 |
+
|
19 |
+
def load_image(url):
|
20 |
+
|
21 |
+
# Mimic a browser request
|
22 |
+
headers = {
|
23 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
|
24 |
+
}
|
25 |
+
|
26 |
+
response = requests.get(url, headers=headers)
|
27 |
+
img = Image.open(BytesIO(response.content))
|
28 |
+
return img
|
29 |
+
|
30 |
+
|
31 |
+
def generate_image_from_grid(grid: np.array, img_size: int = 512) -> Image:
|
32 |
+
"""Generate an iamge from a grid of 0s and 1s"""
|
33 |
+
n = len(grid)
|
34 |
+
cell_pixel_size = img_size // n
|
35 |
+
|
36 |
+
# Create a new image with white background
|
37 |
+
img = Image.new("RGB", (img_size, img_size), "white")
|
38 |
+
pixels = img.load()
|
39 |
+
|
40 |
+
for i in range(n):
|
41 |
+
for j in range(n):
|
42 |
+
# Color a cell black if 0 or white if 1
|
43 |
+
color = (0, 0, 0) if grid[i][j] == 0 else (255, 255, 255)
|
44 |
+
for x in range(cell_pixel_size):
|
45 |
+
for y in range(cell_pixel_size):
|
46 |
+
pixels[j * cell_pixel_size + x, i * cell_pixel_size + y] = color
|
47 |
+
|
48 |
+
return img
|