Spaces:
Runtime error
Runtime error
import io | |
import os | |
import cv2 | |
import base64 | |
from typing import Dict, Any, List, Union, Literal | |
from pathlib import Path | |
import datetime | |
from enum import Enum | |
import numpy as np | |
import requests | |
from PIL import Image | |
PayloadOverrideType = Dict[str, Any] | |
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") | |
test_result_dir = Path(__file__).parent / "results" / f"test_result_{timestamp}" | |
test_expectation_dir = Path(__file__).parent / "expectations" | |
os.makedirs(test_expectation_dir, exist_ok=True) | |
resource_dir = Path(__file__).parents[2] / "images" | |
def read_image(img_path: Path) -> str: | |
img = cv2.imread(str(img_path)) | |
_, bytes = cv2.imencode(".png", img) | |
encoded_image = base64.b64encode(bytes).decode("utf-8") | |
return encoded_image | |
def read_image_dir(img_dir: Path, suffixes=('.png', '.jpg', '.jpeg', '.webp')) -> List[str]: | |
"""Try read all images in given img_dir.""" | |
img_dir = str(img_dir) | |
images = [] | |
for filename in os.listdir(img_dir): | |
if filename.endswith(suffixes): | |
img_path = os.path.join(img_dir, filename) | |
try: | |
images.append(read_image(img_path)) | |
except IOError: | |
print(f"Error opening {img_path}") | |
return images | |
girl_img = read_image(resource_dir / "1girl.png") | |
mask_img = read_image(resource_dir / "mask.png") | |
mask_small_img = read_image(resource_dir / "mask_small.png") | |
portrait_imgs = read_image_dir(resource_dir / "portrait") | |
realistic_girl_face_img = portrait_imgs[0] | |
living_room_img = read_image(resource_dir / "living_room.webp") | |
general_negative_prompt = """ | |
(worst quality:2), (low quality:2), (normal quality:2), lowres, normal quality, | |
((monochrome)), ((grayscale)), skin spots, acnes, skin blemishes, age spot, | |
backlight,(ugly:1.331), (duplicate:1.331), (morbid:1.21), (mutilated:1.21), | |
(tranny:1.331), mutated hands, (poorly drawn hands:1.331), blurry, (bad anatomy:1.21), | |
(bad proportions:1.331), extra limbs, (missing arms:1.331), (extra legs:1.331), | |
(fused fingers:1.61051), (too many fingers:1.61051), (unclear eyes:1.331), bad hands, | |
missing fingers, extra digit, bad body, easynegative, nsfw""" | |
class StableDiffusionVersion(Enum): | |
"""The version family of stable diffusion model.""" | |
UNKNOWN = 0 | |
SD1x = 1 | |
SD2x = 2 | |
SDXL = 3 | |
sd_version = StableDiffusionVersion( | |
int(os.environ.get("CONTROLNET_TEST_SD_VERSION", StableDiffusionVersion.SD1x.value)) | |
) | |
is_full_coverage = os.environ.get("CONTROLNET_TEST_FULL_COVERAGE", None) is not None | |
class APITestTemplate: | |
is_set_expectation_run = os.environ.get("CONTROLNET_SET_EXP", "True") == "True" | |
def __init__( | |
self, | |
name: str, | |
gen_type: Union[Literal["img2img"], Literal["txt2img"]], | |
payload_overrides: PayloadOverrideType, | |
unit_overrides: Union[PayloadOverrideType, List[PayloadOverrideType]], | |
): | |
self.name = name | |
self.url = "http://localhost:7860/sdapi/v1/" + gen_type | |
self.payload = { | |
**(txt2img_payload if gen_type == "txt2img" else img2img_payload), | |
**payload_overrides, | |
} | |
unit_overrides = ( | |
unit_overrides | |
if isinstance(unit_overrides, (list, tuple)) | |
else [unit_overrides] | |
) | |
self.payload["alwayson_scripts"]["ControlNet"]["args"] = [ | |
{ | |
**default_unit, | |
**unit_override, | |
} | |
for unit_override in unit_overrides | |
] | |
def exec(self, result_only: bool = True) -> bool: | |
if not APITestTemplate.is_set_expectation_run: | |
os.makedirs(test_result_dir, exist_ok=True) | |
failed = False | |
response = requests.post(url=self.url, json=self.payload).json() | |
if "images" not in response: | |
print(response) | |
return False | |
dest_dir = ( | |
test_expectation_dir | |
if APITestTemplate.is_set_expectation_run | |
else test_result_dir | |
) | |
results = response["images"][:1] if result_only else response["images"] | |
for i, base64image in enumerate(results): | |
img_file_name = f"{self.name}_{i}.png" | |
Image.open(io.BytesIO(base64.b64decode(base64image.split(",", 1)[0]))).save( | |
dest_dir / img_file_name | |
) | |
if not APITestTemplate.is_set_expectation_run: | |
try: | |
img1 = cv2.imread(os.path.join(test_expectation_dir, img_file_name)) | |
img2 = cv2.imread(os.path.join(test_result_dir, img_file_name)) | |
except Exception as e: | |
print(f"Get exception reading imgs: {e}") | |
failed = True | |
continue | |
if img1 is None: | |
print(f"Warn: No expectation file found {img_file_name}.") | |
continue | |
if not expect_same_image( | |
img1, | |
img2, | |
diff_img_path=str(test_result_dir | |
/ img_file_name.replace(".png", "_diff.png")), | |
): | |
failed = True | |
return not failed | |
def expect_same_image(img1, img2, diff_img_path: str) -> bool: | |
# Calculate the difference between the two images | |
diff = cv2.absdiff(img1, img2) | |
# Set a threshold to highlight the different pixels | |
threshold = 30 | |
diff_highlighted = np.where(diff > threshold, 255, 0).astype(np.uint8) | |
# Assert that the two images are similar within a tolerance | |
similar = np.allclose(img1, img2, rtol=0.5, atol=1) | |
if not similar: | |
# Save the diff_highlighted image to inspect the differences | |
cv2.imwrite(diff_img_path, diff_highlighted) | |
matching_pixels = np.isclose(img1, img2, rtol=0.5, atol=1) | |
similar_in_general = (matching_pixels.sum() / matching_pixels.size) >= 0.95 | |
return similar_in_general | |
default_unit = { | |
"control_mode": 0, | |
"enabled": True, | |
"guidance_end": 1, | |
"guidance_start": 0, | |
"low_vram": False, | |
"pixel_perfect": True, | |
"processor_res": 512, | |
"resize_mode": 1, | |
"threshold_a": 64, | |
"threshold_b": 64, | |
"weight": 1, | |
} | |
img2img_payload = { | |
"batch_size": 1, | |
"cfg_scale": 7, | |
"height": 768, | |
"width": 512, | |
"n_iter": 1, | |
"steps": 10, | |
"sampler_name": "Euler a", | |
"prompt": "(masterpiece: 1.3), (highres: 1.3), best quality,", | |
"negative_prompt": "", | |
"seed": 42, | |
"seed_enable_extras": False, | |
"seed_resize_from_h": 0, | |
"seed_resize_from_w": 0, | |
"subseed": -1, | |
"subseed_strength": 0, | |
"override_settings": {}, | |
"override_settings_restore_afterwards": False, | |
"do_not_save_grid": False, | |
"do_not_save_samples": False, | |
"s_churn": 0, | |
"s_min_uncond": 0, | |
"s_noise": 1, | |
"s_tmax": None, | |
"s_tmin": 0, | |
"script_args": [], | |
"script_name": None, | |
"styles": [], | |
"alwayson_scripts": {"ControlNet": {"args": [default_unit]}}, | |
"denoising_strength": 0.75, | |
"initial_noise_multiplier": 1, | |
"inpaint_full_res": 0, | |
"inpaint_full_res_padding": 32, | |
"inpainting_fill": 1, | |
"inpainting_mask_invert": 0, | |
"mask_blur_x": 4, | |
"mask_blur_y": 4, | |
"mask_blur": 4, | |
"resize_mode": 0, | |
} | |
txt2img_payload = { | |
"alwayson_scripts": {"ControlNet": {"args": [default_unit]}}, | |
"batch_size": 1, | |
"cfg_scale": 7, | |
"comments": {}, | |
"disable_extra_networks": False, | |
"do_not_save_grid": False, | |
"do_not_save_samples": False, | |
"enable_hr": False, | |
"height": 768, | |
"hr_negative_prompt": "", | |
"hr_prompt": "", | |
"hr_resize_x": 0, | |
"hr_resize_y": 0, | |
"hr_scale": 2, | |
"hr_second_pass_steps": 0, | |
"hr_upscaler": "Latent", | |
"n_iter": 1, | |
"negative_prompt": "", | |
"override_settings": {}, | |
"override_settings_restore_afterwards": True, | |
"prompt": "(masterpiece: 1.3), (highres: 1.3), best quality,", | |
"restore_faces": False, | |
"s_churn": 0.0, | |
"s_min_uncond": 0, | |
"s_noise": 1.0, | |
"s_tmax": None, | |
"s_tmin": 0.0, | |
"sampler_name": "Euler a", | |
"script_args": [], | |
"script_name": None, | |
"seed": 42, | |
"seed_enable_extras": True, | |
"seed_resize_from_h": -1, | |
"seed_resize_from_w": -1, | |
"steps": 10, | |
"styles": [], | |
"subseed": -1, | |
"subseed_strength": 0, | |
"tiling": False, | |
"width": 512, | |
} | |