初始化换发型项目:3个微服务代码 + 部署脚本

包含:
- hair_service_sd: 换发型/换发色算法服务 (端口 8801)
- photo_service: LoRA 训练调度服务 (端口 32678)
- stable-diffusion-webui: SD WebUI 推理服务 (端口 57860)
- kohya_ss_home: 训练环境代码
- meidaojia: 监控测试脚本
- setup.sh: 一键部署脚本 (conda环境恢复 + 配置生成 + 完整性检查)
- start_all_services.sh: 启动3个服务
- configure.ini.template: 路径模板化 (BASE_DIR自动推导)
- conda_envs/py310.yml: py310 环境定义

大文件 (weights/, models/, data/, conda_envs/*.tar.gz 等) 通过 .gitignore 排除,
由网盘单独上传。
This commit is contained in:
colomi
2026-07-11 18:11:49 +08:00
commit 0eb61f3e60
628 changed files with 120882 additions and 0 deletions
View File
+36
View File
@@ -0,0 +1,36 @@
import base64
import os
import pytest
test_files_path = os.path.dirname(__file__) + "/test_files"
test_outputs_path = os.path.dirname(__file__) + "/test_outputs"
def pytest_configure(config):
# We don't want to fail on Py.test command line arguments being
# parsed by webui:
os.environ.setdefault("IGNORE_CMD_ARGS_ERRORS", "1")
def file_to_base64(filename):
with open(filename, "rb") as file:
data = file.read()
base64_str = str(base64.b64encode(data), "utf-8")
return "data:image/png;base64," + base64_str
@pytest.fixture(scope="session") # session so we don't read this over and over
def img2img_basic_image_base64() -> str:
return file_to_base64(os.path.join(test_files_path, "img2img_basic.png"))
@pytest.fixture(scope="session") # session so we don't read this over and over
def mask_basic_image_base64() -> str:
return file_to_base64(os.path.join(test_files_path, "mask_basic.png"))
@pytest.fixture(scope="session")
def initialize() -> None:
import webui # noqa: F401
+35
View File
@@ -0,0 +1,35 @@
import requests
def test_simple_upscaling_performed(base_url, img2img_basic_image_base64):
payload = {
"resize_mode": 0,
"show_extras_results": True,
"gfpgan_visibility": 0,
"codeformer_visibility": 0,
"codeformer_weight": 0,
"upscaling_resize": 2,
"upscaling_resize_w": 128,
"upscaling_resize_h": 128,
"upscaling_crop": True,
"upscaler_1": "Lanczos",
"upscaler_2": "None",
"extras_upscaler_2_visibility": 0,
"image": img2img_basic_image_base64,
}
assert requests.post(f"{base_url}/sdapi/v1/extra-single-image", json=payload).status_code == 200
def test_png_info_performed(base_url, img2img_basic_image_base64):
payload = {
"image": img2img_basic_image_base64,
}
assert requests.post(f"{base_url}/sdapi/v1/extra-single-image", json=payload).status_code == 200
def test_interrogate_performed(base_url, img2img_basic_image_base64):
payload = {
"image": img2img_basic_image_base64,
"model": "clip",
}
assert requests.post(f"{base_url}/sdapi/v1/extra-single-image", json=payload).status_code == 200
+29
View File
@@ -0,0 +1,29 @@
import os
from test.conftest import test_files_path, test_outputs_path
import numpy as np
import pytest
from PIL import Image
@pytest.mark.usefixtures("initialize")
@pytest.mark.parametrize("restorer_name", ["gfpgan", "codeformer"])
def test_face_restorers(restorer_name):
from modules import shared
if restorer_name == "gfpgan":
from modules import gfpgan_model
gfpgan_model.setup_model(shared.cmd_opts.gfpgan_models_path)
restorer = gfpgan_model.gfpgan_fix_faces
elif restorer_name == "codeformer":
from modules import codeformer_model
codeformer_model.setup_model(shared.cmd_opts.codeformer_models_path)
restorer = codeformer_model.codeformer.restore
else:
raise NotImplementedError("...")
img = Image.open(os.path.join(test_files_path, "two-faces.jpg"))
np_img = np.array(img, dtype=np.uint8)
fixed_image = restorer(np_img)
assert fixed_image.shape == np_img.shape
assert not np.allclose(fixed_image, np_img) # should have visibly changed
Image.fromarray(fixed_image).save(os.path.join(test_outputs_path, f"{restorer_name}.png"))
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+68
View File
@@ -0,0 +1,68 @@
import pytest
import requests
@pytest.fixture()
def url_img2img(base_url):
return f"{base_url}/sdapi/v1/img2img"
@pytest.fixture()
def simple_img2img_request(img2img_basic_image_base64):
return {
"batch_size": 1,
"cfg_scale": 7,
"denoising_strength": 0.75,
"eta": 0,
"height": 64,
"include_init_images": False,
"init_images": [img2img_basic_image_base64],
"inpaint_full_res": False,
"inpaint_full_res_padding": 0,
"inpainting_fill": 0,
"inpainting_mask_invert": False,
"mask": None,
"mask_blur": 4,
"n_iter": 1,
"negative_prompt": "",
"override_settings": {},
"prompt": "example prompt",
"resize_mode": 0,
"restore_faces": False,
"s_churn": 0,
"s_noise": 1,
"s_tmax": 0,
"s_tmin": 0,
"sampler_index": "Euler a",
"seed": -1,
"seed_resize_from_h": -1,
"seed_resize_from_w": -1,
"steps": 3,
"styles": [],
"subseed": -1,
"subseed_strength": 0,
"tiling": False,
"width": 64,
}
def test_img2img_simple_performed(url_img2img, simple_img2img_request):
assert requests.post(url_img2img, json=simple_img2img_request).status_code == 200
def test_inpainting_masked_performed(url_img2img, simple_img2img_request, mask_basic_image_base64):
simple_img2img_request["mask"] = mask_basic_image_base64
assert requests.post(url_img2img, json=simple_img2img_request).status_code == 200
def test_inpainting_with_inverted_masked_performed(url_img2img, simple_img2img_request, mask_basic_image_base64):
simple_img2img_request["mask"] = mask_basic_image_base64
simple_img2img_request["inpainting_mask_invert"] = True
assert requests.post(url_img2img, json=simple_img2img_request).status_code == 200
def test_img2img_sd_upscale_performed(url_img2img, simple_img2img_request):
simple_img2img_request["script_name"] = "sd upscale"
simple_img2img_request["script_args"] = ["", 8, "Lanczos", 2.0]
assert requests.post(url_img2img, json=simple_img2img_request).status_code == 200
+19
View File
@@ -0,0 +1,19 @@
import types
import pytest
import torch
from modules import torch_utils
@pytest.mark.parametrize("wrapped", [True, False])
def test_get_param(wrapped):
mod = torch.nn.Linear(1, 1)
cpu = torch.device("cpu")
mod.to(dtype=torch.float16, device=cpu)
if wrapped:
# more or less how spandrel wraps a thing
mod = types.SimpleNamespace(model=mod)
p = torch_utils.get_param(mod)
assert p.dtype == torch.float16
assert p.device == cpu
+90
View File
@@ -0,0 +1,90 @@
import pytest
import requests
@pytest.fixture()
def url_txt2img(base_url):
return f"{base_url}/sdapi/v1/txt2img"
@pytest.fixture()
def simple_txt2img_request():
return {
"batch_size": 1,
"cfg_scale": 7,
"denoising_strength": 0,
"enable_hr": False,
"eta": 0,
"firstphase_height": 0,
"firstphase_width": 0,
"height": 64,
"n_iter": 1,
"negative_prompt": "",
"prompt": "example prompt",
"restore_faces": False,
"s_churn": 0,
"s_noise": 1,
"s_tmax": 0,
"s_tmin": 0,
"sampler_index": "Euler a",
"seed": -1,
"seed_resize_from_h": -1,
"seed_resize_from_w": -1,
"steps": 3,
"styles": [],
"subseed": -1,
"subseed_strength": 0,
"tiling": False,
"width": 64,
}
def test_txt2img_simple_performed(url_txt2img, simple_txt2img_request):
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_with_negative_prompt_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["negative_prompt"] = "example negative prompt"
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_with_complex_prompt_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["prompt"] = "((emphasis)), (emphasis1:1.1), [to:1], [from::2], [from:to:0.3], [alt|alt1]"
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_not_square_image_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["height"] = 128
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_with_hrfix_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["enable_hr"] = True
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_with_tiling_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["tiling"] = True
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_with_restore_faces_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["restore_faces"] = True
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
@pytest.mark.parametrize("sampler", ["PLMS", "DDIM", "UniPC"])
def test_txt2img_with_vanilla_sampler_performed(url_txt2img, simple_txt2img_request, sampler):
simple_txt2img_request["sampler_index"] = sampler
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_multiple_batches_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["n_iter"] = 2
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
def test_txt2img_batch_performed(url_txt2img, simple_txt2img_request):
simple_txt2img_request["batch_size"] = 2
assert requests.post(url_txt2img, json=simple_txt2img_request).status_code == 200
+33
View File
@@ -0,0 +1,33 @@
import pytest
import requests
def test_options_write(base_url):
url_options = f"{base_url}/sdapi/v1/options"
response = requests.get(url_options)
assert response.status_code == 200
pre_value = response.json()["send_seed"]
assert requests.post(url_options, json={'send_seed': (not pre_value)}).status_code == 200
response = requests.get(url_options)
assert response.status_code == 200
assert response.json()['send_seed'] == (not pre_value)
requests.post(url_options, json={"send_seed": pre_value})
@pytest.mark.parametrize("url", [
"sdapi/v1/cmd-flags",
"sdapi/v1/samplers",
"sdapi/v1/upscalers",
"sdapi/v1/sd-models",
"sdapi/v1/hypernetworks",
"sdapi/v1/face-restorers",
"sdapi/v1/realesrgan-models",
"sdapi/v1/prompt-styles",
"sdapi/v1/embeddings",
])
def test_get_api_url(base_url, url):
assert requests.get(f"{base_url}/{url}").status_code == 200