sayakpaul HF staff commited on
Commit
873e677
1 Parent(s): 81381bd

redo the space to spice things up.

Browse files
Files changed (4) hide show
  1. app.py +139 -143
  2. image_utils.py +26 -0
  3. metrics_utils.py +48 -0
  4. report_utils.py +47 -0
app.py CHANGED
@@ -1,93 +1,41 @@
1
  import importlib
2
- from functools import partial
3
  from typing import List
4
 
5
  import gradio as gr
6
  import numpy as np
7
  import torch
8
  from diffusers import StableDiffusionPipeline
9
- from PIL import Image
10
- from torchmetrics.functional.multimodal import clip_score
11
- from torchmetrics.image.inception import InceptionScore
 
 
12
 
13
  SEED = 0
14
  WEIGHT_DTYPE = torch.float16
15
 
16
  TITLE = "Evaluate Schedulers with StableDiffusionPipeline 🧨"
17
- DESCRIPTION = """
18
  This Space allows you to quantitatively compare [different noise schedulers](https://huggingface.co/docs/diffusers/using-diffusers/schedulers) with a [`StableDiffusionPipeline`](https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion/overview).
19
 
20
  One of the applications of this Space could be to evaluate different schedulers for a certain Stable Diffusion checkpoint for a fixed number of inference steps.
21
-
22
- Here's how it works:
23
-
24
  * The evaluator first sets a seed and then generates the initial noise which is passed as the initial latent to start the image generation process. It is done to ensure fair comparison.
25
  * This initial latent is used every time the pipeline is run (with different schedulers).
26
  * To quantify the quality of the generated images we use:
27
  * [Inception Score](https://en.wikipedia.org/wiki/Inception_score)
28
  * [Clip Score](https://arxiv.org/abs/2104.08718)
29
-
30
- **Notes**:
31
-
32
  * The default scheduler associated with the provided checkpoint is always used for reporting the scores.
33
  * Increasing both the number of images per prompt and the number of inference steps could quickly build up the inference queue and thus
34
  resulting in slowdowns.
35
  """
36
 
37
-
38
- inception_score_fn = InceptionScore(normalize=True)
39
- torch.manual_seed(SEED)
40
- clip_score_fn = partial(clip_score, model_name_or_path="openai/clip-vit-base-patch16")
41
-
42
-
43
- def make_grid(images, rows, cols):
44
- w, h = images[0].size
45
- grid = Image.new("RGB", size=(cols * w, rows * h))
46
- for i, image in enumerate(images):
47
- grid.paste(image, box=(i % cols * w, i // cols * h))
48
- return grid
49
-
50
-
51
- # Copied from https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/pipeline_utils.py#L814
52
- def numpy_to_pil(images):
53
- """
54
- Convert a numpy image or a batch of images to a PIL image.
55
- """
56
- if images.ndim == 3:
57
- images = images[None, ...]
58
- images = (images * 255).round().astype("uint8")
59
- if images.shape[-1] == 1:
60
- # special case for grayscale (single channel) images
61
- pil_images = [Image.fromarray(image.squeeze(), mode="L") for image in images]
62
- else:
63
- pil_images = [Image.fromarray(image) for image in images]
64
-
65
- return pil_images
66
-
67
-
68
- def prepare_report(scheduler_name: str, results: dict):
69
- image_grid = results["images"]
70
- scores = results["scores"]
71
- img_str = ""
72
-
73
- image_name = f"{scheduler_name}_images.png"
74
- image_grid.save(image_name)
75
- img_str = img_str = f"![img_grid_{scheduler_name}](/file=./{image_name})\n"
76
-
77
- report_str = f"""
78
- \n\n## {scheduler_name}
79
-
80
- ### Sample images
81
-
82
- {img_str}
83
-
84
- ### Scores
85
-
86
- {scores}
87
- \n\n
88
- """
89
-
90
- return report_str
91
 
92
 
93
  def initialize_pipeline(checkpoint: str):
@@ -115,63 +63,51 @@ def get_latents(num_images_per_prompt: int, seed=SEED):
115
  return latents
116
 
117
 
118
- def compute_metrics(images: np.ndarray, prompts: List[str]):
119
- inception_score_fn.update(torch.from_numpy(images).permute(0, 3, 1, 2))
120
- inception_score = inception_score_fn.compute()
121
-
122
- images_int = (images * 255).astype("uint8")
123
- clip_score = clip_score_fn(
124
- torch.from_numpy(images_int).permute(0, 3, 1, 2), prompts
125
- ).detach()
126
- return {
127
- "inception_score (⬆️)": {
128
- "mean": round(float(inception_score[0]), 4),
129
- "std": round(float(inception_score[1]), 4),
130
- },
131
- "clip_score (⬆️)": round(float(clip_score), 4),
132
- }
133
-
134
-
135
  def run(
136
  prompt: str,
137
  num_images_per_prompt: int,
138
  num_inference_steps: int,
139
  checkpoint: str,
140
- schedulers_to_test: List[str],
 
 
 
 
141
  ):
 
 
 
 
 
 
 
 
142
  all_images = {}
 
143
 
 
144
  sd_pipeline, original_scheduler_config = initialize_pipeline(checkpoint)
 
 
 
145
  latents = get_latents(num_images_per_prompt)
146
  prompts = [prompt] * num_images_per_prompt
147
 
148
- images = sd_pipeline(
149
- prompts,
150
- latents=latents,
151
- num_inference_steps=num_inference_steps,
152
- output_type="numpy",
153
- ).images
154
  original_scheduler_name = original_scheduler_config._class_name
 
155
 
156
- all_images.update(
157
- {
158
- original_scheduler_name: {
159
- "images": make_grid(numpy_to_pil(images), 1, num_images_per_prompt),
160
- "scores": compute_metrics(images, prompts),
161
- }
162
- }
163
- )
164
- # print("First scheduler complete.")
165
-
166
- for scheduler_name in schedulers_to_test:
167
- if scheduler_name == original_scheduler_name:
168
- continue
169
- scheduler_cls = get_scheduler(scheduler_name)
170
- current_scheduler = scheduler_cls.from_config(original_scheduler_config)
171
- sd_pipeline.scheduler = current_scheduler
172
 
173
  cur_scheduler_images = sd_pipeline(
174
- prompts, num_inference_steps=num_inference_steps, output_type="numpy"
 
 
 
175
  ).images
176
  all_images.update(
177
  {
@@ -179,51 +115,111 @@ def run(
179
  "images": make_grid(
180
  numpy_to_pil(cur_scheduler_images), 1, num_images_per_prompt
181
  ),
182
- "scores": compute_metrics(cur_scheduler_images, prompts),
183
  }
184
  }
185
  )
186
- # print(f"{scheduler_name} complete.")
 
187
 
 
188
  output_str = ""
189
  for scheduler_name in all_images:
190
- # print(f"scheduler_name: {scheduler_name}")
191
  output_str += prepare_report(scheduler_name, all_images[scheduler_name])
192
- # print(output_str)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  return output_str
194
 
195
 
196
- demo = gr.Interface(
197
- run,
198
- inputs=[
199
- gr.Text(max_lines=1, placeholder="a painting of a dog"),
200
- gr.Slider(3, 10, value=3, step=1),
201
- gr.Slider(10, 100, value=50, step=1),
202
- gr.Dropdown(
203
- [
204
- "CompVis/stable-diffusion-v1-4",
205
- "runwayml/stable-diffusion-v1-5",
206
- "stabilityai/stable-diffusion-2-base",
207
- ],
208
- value="CompVis/stable-diffusion-v1-4",
209
- multiselect=False,
210
- interactive=True,
211
- ),
212
- gr.Dropdown(
213
- [
214
- "EulerDiscreteScheduler",
215
- "PNDMScheduler",
216
- "LMSDiscreteScheduler",
217
- "DPMSolverMultistepScheduler",
218
- "DDIMScheduler",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  ],
220
- value=["LMSDiscreteScheduler"],
221
- multiselect=True,
222
- ),
223
- ],
224
- outputs=[gr.Markdown().style()],
225
- title=TITLE,
226
- description=DESCRIPTION,
227
- allow_flagging=False,
228
- )
229
- demo.launch()
 
1
  import importlib
 
2
  from typing import List
3
 
4
  import gradio as gr
5
  import numpy as np
6
  import torch
7
  from diffusers import StableDiffusionPipeline
8
+ from torchmetrics import PeakSignalNoiseRatio, StructuralSimilarityIndexMeasure
9
+
10
+ from image_utils import make_grid, numpy_to_pil
11
+ from metrics_utils import compute_main_metrics, compute_psnr_or_ssim
12
+ from report_utils import add_psnr_ssim_to_report, prepare_report
13
 
14
  SEED = 0
15
  WEIGHT_DTYPE = torch.float16
16
 
17
  TITLE = "Evaluate Schedulers with StableDiffusionPipeline 🧨"
18
+ ABSTRACT = """
19
  This Space allows you to quantitatively compare [different noise schedulers](https://huggingface.co/docs/diffusers/using-diffusers/schedulers) with a [`StableDiffusionPipeline`](https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion/overview).
20
 
21
  One of the applications of this Space could be to evaluate different schedulers for a certain Stable Diffusion checkpoint for a fixed number of inference steps.
22
+ """
23
+ DESCRIPTION = """
24
+ #### Hoes does it work?
25
  * The evaluator first sets a seed and then generates the initial noise which is passed as the initial latent to start the image generation process. It is done to ensure fair comparison.
26
  * This initial latent is used every time the pipeline is run (with different schedulers).
27
  * To quantify the quality of the generated images we use:
28
  * [Inception Score](https://en.wikipedia.org/wiki/Inception_score)
29
  * [Clip Score](https://arxiv.org/abs/2104.08718)
30
+ #### Notes
31
+ * When selecting a model checkpoint, if you select "Other" you will have the option to provide a custom Stable Diffusion checkpoint.
 
32
  * The default scheduler associated with the provided checkpoint is always used for reporting the scores.
33
  * Increasing both the number of images per prompt and the number of inference steps could quickly build up the inference queue and thus
34
  resulting in slowdowns.
35
  """
36
 
37
+ psnr_fn = PeakSignalNoiseRatio()
38
+ ssim_fn = StructuralSimilarityIndexMeasure()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
 
41
  def initialize_pipeline(checkpoint: str):
 
63
  return latents
64
 
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  def run(
67
  prompt: str,
68
  num_images_per_prompt: int,
69
  num_inference_steps: int,
70
  checkpoint: str,
71
+ other_finedtuned_checkpoints: str = None,
72
+ schedulers_to_test: List[str] = None,
73
+ ssim: bool = False,
74
+ psnr: bool = False,
75
+ progress=gr.Progress(),
76
  ):
77
+ progress(0, desc="Starting...")
78
+
79
+ if checkpoint == "Other" and other_finedtuned_checkpoints == "":
80
+ return "❌ No legit checkpoint provided ❌"
81
+
82
+ elif checkpoint == "Other":
83
+ checkpoint = other_finedtuned_checkpoints
84
+
85
  all_images = {}
86
+ scheduler_images = {}
87
 
88
+ # Set up the pipeline
89
  sd_pipeline, original_scheduler_config = initialize_pipeline(checkpoint)
90
+ sd_pipeline.set_progress_bar_config(disable=True)
91
+
92
+ # Prepare latents to start generation and the prompts.
93
  latents = get_latents(num_images_per_prompt)
94
  prompts = [prompt] * num_images_per_prompt
95
 
 
 
 
 
 
 
96
  original_scheduler_name = original_scheduler_config._class_name
97
+ schedulers_to_test.append(original_scheduler_name)
98
 
99
+ # Start generating the images and computing their scores.
100
+ for scheduler_name in progress.tqdm(schedulers_to_test):
101
+ if scheduler_name != original_scheduler_name:
102
+ scheduler_cls = get_scheduler(scheduler_name)
103
+ current_scheduler = scheduler_cls.from_config(original_scheduler_config)
104
+ sd_pipeline.scheduler = current_scheduler
 
 
 
 
 
 
 
 
 
 
105
 
106
  cur_scheduler_images = sd_pipeline(
107
+ prompts,
108
+ latents=latents,
109
+ num_inference_steps=num_inference_steps,
110
+ output_type="numpy",
111
  ).images
112
  all_images.update(
113
  {
 
115
  "images": make_grid(
116
  numpy_to_pil(cur_scheduler_images), 1, num_images_per_prompt
117
  ),
118
+ "scores": compute_main_metrics(cur_scheduler_images, prompts),
119
  }
120
  }
121
  )
122
+ scheduler_images.update({scheduler_name: cur_scheduler_images})
123
+ torch.cuda.empty_cache()
124
 
125
+ # Prepare output report.
126
  output_str = ""
127
  for scheduler_name in all_images:
 
128
  output_str += prepare_report(scheduler_name, all_images[scheduler_name])
129
+
130
+ # Append PSNR or SSIM if needed.
131
+ if len(schedulers_to_test) > 1:
132
+ ssim_scores = psnr_scores = None
133
+ if ssim:
134
+ ssim_scores = compute_psnr_or_ssim(
135
+ ssim_fn, scheduler_images, original_scheduler_name
136
+ )
137
+ if psnr:
138
+ psnr_scores = compute_psnr_or_ssim(
139
+ psnr_fn, scheduler_images, original_scheduler_name
140
+ )
141
+
142
+ if len(schedulers_to_test) > 1:
143
+ ssim_psnr_str = add_psnr_ssim_to_report(
144
+ original_scheduler_name, ssim_scores, psnr_scores
145
+ )
146
+ if ssim_psnr_str != "":
147
+ output_str += ssim_psnr_str
148
+
149
  return output_str
150
 
151
 
152
+ with gr.Blocks(title="Scheduler Evaluation") as demo:
153
+ gr.Markdown(f"## {TITLE}\n\n\n\n{ABSTRACT}")
154
+
155
+ with gr.Row():
156
+ with gr.Column():
157
+ prompt = gr.Text(
158
+ max_lines=1, placeholder="a painting of a dog", label="prompt"
159
+ )
160
+ num_images_per_prompt = gr.Slider(
161
+ 3, 10, value=3, step=1, label="num_images_per_prompt"
162
+ )
163
+ num_inference_steps = gr.Slider(
164
+ 10, 100, value=50, step=1, label="num_inference_steps"
165
+ )
166
+ model_ckpt = gr.Dropdown(
167
+ [
168
+ "CompVis/stable-diffusion-v1-4",
169
+ "runwayml/stable-diffusion-v1-5",
170
+ "stabilityai/stable-diffusion-2-base",
171
+ "Other",
172
+ ],
173
+ value="CompVis/stable-diffusion-v1-4",
174
+ multiselect=False,
175
+ interactive=True,
176
+ label="model_ckpt",
177
+ )
178
+ other_finedtuned_checkpoints = gr.Textbox(
179
+ visible=False,
180
+ interactive=True,
181
+ placeholder="valhalla/sd-pokemon-model",
182
+ label="custom_checkpoint",
183
+ )
184
+ model_ckpt.change(
185
+ lambda x: gr.Dropdown.update(visible=x == "Other"),
186
+ model_ckpt,
187
+ other_finedtuned_checkpoints,
188
+ )
189
+ schedulers_to_test = gr.Dropdown(
190
+ [
191
+ "EulerDiscreteScheduler",
192
+ "PNDMScheduler",
193
+ "LMSDiscreteScheduler",
194
+ "DPMSolverMultistepScheduler",
195
+ "DDIMScheduler",
196
+ ],
197
+ value=["LMSDiscreteScheduler"],
198
+ multiselect=True,
199
+ label="schedulers_to_test",
200
+ )
201
+ ssim = gr.Checkbox(label="Compute SSIM")
202
+ psnr = gr.Checkbox(label="Compute PSNR")
203
+ evaluation_button = gr.Button(value="Submit")
204
+
205
+ with gr.Column():
206
+ report = gr.Markdown(label="Evaluation Report").style()
207
+
208
+ evaluation_button.click(
209
+ run,
210
+ inputs=[
211
+ prompt,
212
+ num_images_per_prompt,
213
+ num_inference_steps,
214
+ model_ckpt,
215
+ other_finedtuned_checkpoints,
216
+ schedulers_to_test,
217
+ ssim,
218
+ psnr,
219
  ],
220
+ outputs=report,
221
+ )
222
+
223
+ gr.Markdown(f"{DESCRIPTION}")
224
+
225
+ demo.queue().launch(debug=True)
 
 
 
 
image_utils.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+
3
+
4
+ def make_grid(images, rows, cols):
5
+ w, h = images[0].size
6
+ grid = Image.new("RGB", size=(cols * w, rows * h))
7
+ for i, image in enumerate(images):
8
+ grid.paste(image, box=(i % cols * w, i // cols * h))
9
+ return grid
10
+
11
+
12
+ # Copied from https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/pipeline_utils.py#L814
13
+ def numpy_to_pil(images):
14
+ """
15
+ Convert a numpy image or a batch of images to a PIL image.
16
+ """
17
+ if images.ndim == 3:
18
+ images = images[None, ...]
19
+ images = (images * 255).round().astype("uint8")
20
+ if images.shape[-1] == 1:
21
+ # special case for grayscale (single channel) images
22
+ pil_images = [Image.fromarray(image.squeeze(), mode="L") for image in images]
23
+ else:
24
+ pil_images = [Image.fromarray(image) for image in images]
25
+
26
+ return pil_images
metrics_utils.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import partial
2
+ from typing import Callable, Dict, List
3
+
4
+ import numpy as np
5
+ import torch
6
+ from torchmetrics.functional.multimodal import clip_score
7
+ from torchmetrics.image.inception import InceptionScore
8
+
9
+ SEED = 0
10
+
11
+ inception_score_fn = InceptionScore(normalize=True)
12
+ torch.manual_seed(SEED)
13
+ clip_score_fn = partial(clip_score, model_name_or_path="openai/clip-vit-base-patch16")
14
+
15
+
16
+ def compute_main_metrics(images: np.ndarray, prompts: List[str]) -> Dict:
17
+ inception_score_fn.update(torch.from_numpy(images).permute(0, 3, 1, 2))
18
+ inception_score = inception_score_fn.compute()
19
+
20
+ images_int = (images * 255).astype("uint8")
21
+ clip_score = clip_score_fn(
22
+ torch.from_numpy(images_int).permute(0, 3, 1, 2), prompts
23
+ ).detach()
24
+ return {
25
+ "inception_score (⬆️)": {
26
+ "mean": round(float(inception_score[0]), 4),
27
+ "std": round(float(inception_score[1]), 4),
28
+ },
29
+ "clip_score (⬆️)": round(float(clip_score), 4),
30
+ }
31
+
32
+
33
+ def compute_psnr_or_ssim(
34
+ fn: Callable, images_dict: Dict, original_scheduler_name: str
35
+ ) -> Dict:
36
+ result_dict = {}
37
+ original_scheduler_images = images_dict[original_scheduler_name]
38
+ original_scheduler_images = torch.from_numpy(original_scheduler_images).permute(
39
+ 0, 3, 1, 2
40
+ )
41
+ for k in images_dict:
42
+ if k != original_scheduler_name:
43
+ current_scheduler_images = torch.from_numpy(images_dict[k]).permute(
44
+ 0, 3, 1, 2
45
+ )
46
+ current_value = fn(current_scheduler_images, original_scheduler_images)
47
+ result_dict.update({k: round(float(current_value), 4)})
48
+ return result_dict
report_utils.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+
4
+ def prepare_report(scheduler_name: str, results: dict):
5
+ image_grid = results["images"]
6
+ scores = results["scores"]
7
+ img_str = ""
8
+
9
+ image_name = f"{scheduler_name}_images.png"
10
+ image_grid.save(image_name)
11
+ img_str = img_str = f"![img_grid_{scheduler_name}](/file=./{image_name})\n"
12
+
13
+ report_str = f"""
14
+ \n\n## {scheduler_name}
15
+
16
+ ### Sample images
17
+
18
+ {img_str}
19
+
20
+ ### Scores
21
+
22
+ {scores}
23
+ \n\n
24
+ """
25
+
26
+ return report_str
27
+
28
+
29
+ def add_psnr_ssim_to_report(
30
+ original_scheduler_name: str, ssim_scores: Dict = None, psnr_scores: Dict = None
31
+ ) -> str:
32
+ current_str = ""
33
+ if ssim_scores is not None:
34
+ current_str += f"""
35
+ \n\n
36
+ ## SSIM
37
+
38
+ SSIM computed w.r.t the images generated with {original_scheduler_name}:\n\n {json.dumps(ssim_scores, indent=6)}
39
+ """
40
+ if psnr_scores is not None:
41
+ current_str += f"""
42
+ \n\n
43
+ ## PSNR
44
+
45
+ PSNR computed w.r.t the images generated with {original_scheduler_name}:\n\n {json.dumps(psnr_scores, indent=6)}
46
+ """
47
+ return current_str