jerpint commited on
Commit
ea2f505
1 Parent(s): e955cd2

first commit

Browse files
Files changed (6) hide show
  1. app.py +163 -0
  2. controlnet.py +67 -0
  3. game_of_life.py +97 -0
  4. requirements.txt +7 -0
  5. sky-gol-image.jpeg +0 -0
  6. 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