upload a cn

This commit is contained in:
lllyasviel
2024-01-27 10:34:31 -08:00
parent ef02d8fa39
commit ccea2f3305
940 changed files with 150956 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
# Tests
There are 2 types of tests:
- unittest: backend based tests that directly import A1111 shared modules
- api test: test functionality through A1111 web API
# Run tests locally
Make sure the current working directory is A1111 root.
## Install test dependencies
`pip install -r requirements-test.txt`
## Start test server
```shell
python -m coverage run
--data-file=.coverage.server
launch.py
--skip-prepare-environment
--skip-torch-cuda-test
--test-server
--do-not-download-clip
--no-half
--disable-opt-split-attention
--use-cpu all
--api-server-stop
```
## Setting environment variables
Setting `CONTROLNET_TEST_SD_VERSION` for stable diffusion model family used during testing.
- 1 for SD1.x
- 2 for SD2.x
- 3 for SDXL
## Run test
```shell
python -m pytest -vv --junitxml=test/results.xml --cov ./extensions/sd-webui-controlnet --cov-report=xml --verify-base-url ./extensions/sd-webui-controlnet/tests
```
## Check code coverage
Text report
```shell
python -m coverage report -i
```
HTML report
```shell
python -m coverage html -i
```

View File

@@ -0,0 +1,50 @@
import unittest
import numpy as np
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from annotator.openpose.body import Body, Keypoint, BodyResult
class TestFormatBodyResult(unittest.TestCase):
def setUp(self):
self.candidate = np.array([
[10, 20, 0.9, 0],
[30, 40, 0.8, 1],
[50, 60, 0.7, 2],
[70, 80, 0.6, 3]
])
self.subset = np.array([
[-1, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1.7, 2],
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 0.6, 1]
])
def test_format_body_result(self):
expected_result = [
BodyResult(
keypoints=[
None,
Keypoint(x=10, y=20, score=0.9, id=0),
Keypoint(x=30, y=40, score=0.8, id=1),
None
] + [None] * 14,
total_score=1.7,
total_parts=2
),
BodyResult(
keypoints=[None] * 17 + [
Keypoint(x=70, y=80, score=0.6, id=3)
],
total_score=0.6,
total_parts=1
)
]
result = Body.format_body_result(self.candidate, self.subset)
self.assertEqual(result, expected_result)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,109 @@
import unittest
import numpy as np
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from annotator.openpose.util import faceDetect, handDetect
from annotator.openpose.body import Keypoint, BodyResult
class TestFaceDetect(unittest.TestCase):
def test_no_faces(self):
oriImg = np.zeros((100, 100, 3), dtype=np.uint8)
body = BodyResult([None] * 18, total_score=3, total_parts=0)
expected_result = None
result = faceDetect(body, oriImg)
self.assertEqual(result, expected_result)
def test_single_face(self):
body = BodyResult([
Keypoint(50, 50),
*([None] * 13),
Keypoint(30, 40),
Keypoint(70, 40),
Keypoint(20, 50),
Keypoint(80, 50),
], total_score=2, total_parts=5)
oriImg = np.zeros((100, 100, 3), dtype=np.uint8)
expected_result = (0, 0, 120)
result = faceDetect(body, oriImg)
self.assertEqual(result, expected_result)
class TestHandDetect(unittest.TestCase):
def test_no_hands(self):
oriImg = np.zeros((100, 100, 3), dtype=np.uint8)
body = BodyResult([None] * 18, total_score=3, total_parts=0)
expected_result = []
result = handDetect(body, oriImg)
self.assertEqual(result, expected_result)
def test_single_left_hand(self):
oriImg = np.zeros((100, 100, 3), dtype=np.uint8)
body = BodyResult([
None, None, None, None, None,
Keypoint(20, 20),
Keypoint(40, 30),
Keypoint(60, 40),
*([None] * 8),
Keypoint(20, 60),
Keypoint(40, 70),
Keypoint(60, 80)
], total_score=3, total_parts=0.5)
expected_result = [(49, 26, 33, True)]
result = handDetect(body, oriImg)
self.assertEqual(result, expected_result)
def test_single_right_hand(self):
oriImg = np.zeros((100, 100, 3), dtype=np.uint8)
body = BodyResult([
None, None,
Keypoint(20, 20),
Keypoint(40, 30),
Keypoint(60, 40),
*([None] * 11),
Keypoint(20, 60),
Keypoint(40, 70),
Keypoint(60, 80)
], total_score=3, total_parts=0.5)
expected_result = [(49, 26, 33, False)]
result = handDetect(body, oriImg)
self.assertEqual(result, expected_result)
def test_multiple_hands(self):
body = BodyResult([
Keypoint(20, 20),
Keypoint(40, 30),
Keypoint(60, 40),
Keypoint(20, 60),
Keypoint(40, 70),
Keypoint(60, 80),
Keypoint(10, 10),
Keypoint(30, 20),
Keypoint(50, 30),
Keypoint(10, 50),
Keypoint(30, 60),
Keypoint(50, 70),
*([None] * 6),
], total_score=3, total_parts=0.5)
oriImg = np.zeros((100, 100, 3), dtype=np.uint8)
expected_result = [(0, 0, 100, True), (16, 43, 56, False)]
result = handDetect(body, oriImg)
self.assertEqual(result, expected_result)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,83 @@
import unittest
import numpy as np
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from annotator.openpose import encode_poses_as_json, HumanPoseResult, Keypoint
from annotator.openpose.body import BodyResult
class TestEncodePosesAsJson(unittest.TestCase):
def test_empty_list(self):
poses = []
canvas_height = 1080
canvas_width = 1920
result = encode_poses_as_json(poses, [], canvas_height, canvas_width)
expected = {
'people': [],
'animals': [],
'canvas_height': canvas_height,
'canvas_width': canvas_width,
}
self.assertDictEqual(result, expected)
def test_single_pose_no_keypoints(self):
poses = [HumanPoseResult(BodyResult(None, 0, 0), None, None, None)]
canvas_height = 1080
canvas_width = 1920
result = encode_poses_as_json(poses, [],canvas_height, canvas_width)
expected = {
'people': [
{
'pose_keypoints_2d': None,
'face_keypoints_2d': None,
'hand_left_keypoints_2d': None,
'hand_right_keypoints_2d': None,
},
],
'animals': [],
'canvas_height': canvas_height,
'canvas_width': canvas_width,
}
self.assertDictEqual(result, expected)
def test_single_pose_with_keypoints(self):
keypoints = [Keypoint(np.float32(0.5), np.float32(0.5)), None, Keypoint(0.6, 0.6)]
poses = [HumanPoseResult(BodyResult(keypoints, 0, 0), keypoints, keypoints, keypoints)]
canvas_height = 1080
canvas_width = 1920
result = encode_poses_as_json(poses, [], canvas_height, canvas_width)
expected = {
'people': [
{
'pose_keypoints_2d': [
0.5, 0.5, 1.0,
0.0, 0.0, 0.0,
0.6, 0.6, 1.0,
],
'face_keypoints_2d': [
0.5, 0.5, 1.0,
0.0, 0.0, 0.0,
0.6, 0.6, 1.0,
],
'hand_left_keypoints_2d': [
0.5, 0.5, 1.0,
0.0, 0.0, 0.0,
0.6, 0.6, 1.0,
],
'hand_right_keypoints_2d': [
0.5, 0.5, 1.0,
0.0, 0.0, 0.0,
0.6, 0.6, 1.0,
],
},
],
'animals': [],
'canvas_height': canvas_height,
'canvas_width': canvas_width,
}
self.assertDictEqual(result, expected)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,115 @@
"""
Disabled because unloading openpose detection models is flaky.
See https://github.com/Mikubill/sd-webui-controlnet/actions/runs/6758718106/job/18370634881
for CI report of an example flaky run.
"""
import unittest
import cv2
import numpy as np
from pathlib import Path
from typing import Dict
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from annotator.openpose import OpenposeDetector
class TestOpenposeDetector(unittest.TestCase):
image_path = str(Path(__file__).parent.parent.parent / 'images')
def setUp(self) -> None:
self.detector = OpenposeDetector()
self.detector.load_model()
def tearDown(self) -> None:
self.detector.unload_model()
def expect_same_image(self, img1, img2, diff_img_path: str):
# 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=1e-05, atol=1e-08)
if not similar:
# Save the diff_highlighted image to inspect the differences
cv2.imwrite(diff_img_path, cv2.cvtColor(diff_highlighted, cv2.COLOR_RGB2BGR))
self.assertTrue(similar)
# Save expectation image as png so that no compression issue happens.
def template(self, test_image: str, expected_image: str, detector_config: Dict, overwrite_expectation: bool = False):
oriImg = cv2.cvtColor(cv2.imread(test_image), cv2.COLOR_BGR2RGB)
canvas = self.detector(oriImg, **detector_config)
# Create expectation file
if overwrite_expectation:
cv2.imwrite(expected_image, cv2.cvtColor(canvas, cv2.COLOR_RGB2BGR))
else:
expected_canvas = cv2.cvtColor(cv2.imread(expected_image), cv2.COLOR_BGR2RGB)
self.expect_same_image(canvas, expected_canvas, diff_img_path=expected_image.replace('.png', '_diff.png'))
def test_body(self):
self.template(
test_image = f'{TestOpenposeDetector.image_path}/ski.jpg',
expected_image = f'{TestOpenposeDetector.image_path}/expected_ski_output.png',
detector_config=dict(),
overwrite_expectation=False
)
def test_hand(self):
self.template(
test_image = f'{TestOpenposeDetector.image_path}/woman.jpeg',
expected_image = f'{TestOpenposeDetector.image_path}/expected_woman_hand_output.png',
detector_config=dict(
include_body=False,
include_face=False,
include_hand=True,
),
overwrite_expectation=False
)
def test_face(self):
self.template(
test_image = f'{TestOpenposeDetector.image_path}/woman.jpeg',
expected_image = f'{TestOpenposeDetector.image_path}/expected_woman_face_output.png',
detector_config=dict(
include_body=False,
include_face=True,
include_hand=False,
),
overwrite_expectation=False
)
def test_all(self):
self.template(
test_image = f'{TestOpenposeDetector.image_path}/woman.jpeg',
expected_image = f'{TestOpenposeDetector.image_path}/expected_woman_all_output.png',
detector_config=dict(
include_body=True,
include_face=True,
include_hand=True,
),
overwrite_expectation=False
)
def test_dw(self):
self.template(
test_image = f'{TestOpenposeDetector.image_path}/woman.jpeg',
expected_image = f'{TestOpenposeDetector.image_path}/expected_woman_dw_all_output.png',
detector_config=dict(
include_body=True,
include_face=True,
include_hand=True,
use_dw_pose=True,
),
overwrite_expectation=False,
)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,337 @@
import unittest.mock
import importlib
from typing import Any
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from modules import processing, scripts, shared
from scripts import controlnet, external_code, batch_hijack
batch_hijack.instance.undo_hijack()
original_process_images_inner = processing.process_images_inner
class TestBatchHijack(unittest.TestCase):
@unittest.mock.patch('modules.script_callbacks.on_script_unloaded')
def setUp(self, on_script_unloaded_mock):
self.on_script_unloaded_mock = on_script_unloaded_mock
self.batch_hijack_object = batch_hijack.BatchHijack()
self.batch_hijack_object.do_hijack()
def tearDown(self):
self.batch_hijack_object.undo_hijack()
def test_do_hijack__registers_on_script_unloaded(self):
self.on_script_unloaded_mock.assert_called_once_with(self.batch_hijack_object.undo_hijack)
def test_do_hijack__call_once__hijacks_once(self):
self.assertEqual(getattr(processing, '__controlnet_original_process_images_inner'), original_process_images_inner)
self.assertEqual(processing.process_images_inner, self.batch_hijack_object.processing_process_images_hijack)
@unittest.mock.patch('modules.processing.__controlnet_original_process_images_inner')
def test_do_hijack__multiple_times__hijacks_once(self, process_images_inner_mock):
self.batch_hijack_object.do_hijack()
self.batch_hijack_object.do_hijack()
self.batch_hijack_object.do_hijack()
self.assertEqual(process_images_inner_mock, getattr(processing, '__controlnet_original_process_images_inner'))
class TestGetControlNetBatchesWorks(unittest.TestCase):
def setUp(self):
self.p = unittest.mock.MagicMock()
assert scripts.scripts_txt2img is not None
self.p.scripts = scripts.scripts_txt2img
self.cn_script = controlnet.Script()
self.p.scripts.alwayson_scripts = [self.cn_script]
self.p.script_args = []
def tearDown(self):
batch_hijack.instance.dispatch_callbacks(batch_hijack.instance.postprocess_batch_callbacks, self.p)
def assert_get_cn_batches_works(self, batch_images_list):
self.cn_script.args_from = 0
self.cn_script.args_to = self.cn_script.args_from + len(self.p.script_args)
is_cn_batch, batches, output_dir, _ = batch_hijack.get_cn_batches(self.p)
batch_hijack.instance.dispatch_callbacks(batch_hijack.instance.process_batch_callbacks, self.p, batches, output_dir)
batch_units = [unit for unit in self.p.script_args if getattr(unit, 'input_mode', batch_hijack.InputMode.SIMPLE) == batch_hijack.InputMode.BATCH]
if batch_units:
self.assertEqual(min(len(list(unit.batch_images)) for unit in batch_units), len(batches))
else:
self.assertEqual(1, len(batches))
for i, unit in enumerate(self.cn_script.enabled_units):
self.assertListEqual(batch_images_list[i], list(unit.batch_images))
def test_get_cn_batches__empty(self):
is_batch, batches, _, _ = batch_hijack.get_cn_batches(self.p)
self.assertEqual(1, len(batches))
self.assertEqual(is_batch, False)
def test_get_cn_batches__1_simple(self):
self.p.script_args.append(external_code.ControlNetUnit(image=get_dummy_image()))
self.assert_get_cn_batches_works([
[self.p.script_args[0].image],
])
def test_get_cn_batches__2_simples(self):
self.p.script_args.extend([
external_code.ControlNetUnit(image=get_dummy_image(0)),
external_code.ControlNetUnit(image=get_dummy_image(1)),
])
self.assert_get_cn_batches_works([
[get_dummy_image(0)],
[get_dummy_image(1)],
])
def test_get_cn_batches__1_batch(self):
self.p.script_args.extend([
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(0),
get_dummy_image(1),
],
),
])
self.assert_get_cn_batches_works([
[
get_dummy_image(0),
get_dummy_image(1),
],
])
def test_get_cn_batches__2_batches(self):
self.p.script_args.extend([
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(0),
get_dummy_image(1),
],
),
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(2),
get_dummy_image(3),
],
),
])
self.assert_get_cn_batches_works([
[
get_dummy_image(0),
get_dummy_image(1),
],
[
get_dummy_image(2),
get_dummy_image(3),
],
])
def test_get_cn_batches__2_mixed(self):
self.p.script_args.extend([
external_code.ControlNetUnit(image=get_dummy_image(0)),
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(1),
get_dummy_image(2),
],
),
])
self.assert_get_cn_batches_works([
[
get_dummy_image(0),
get_dummy_image(0),
],
[
get_dummy_image(1),
get_dummy_image(2),
],
])
def test_get_cn_batches__3_mixed(self):
self.p.script_args.extend([
external_code.ControlNetUnit(image=get_dummy_image(0)),
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(1),
get_dummy_image(2),
get_dummy_image(3),
],
),
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(4),
get_dummy_image(5),
],
),
])
self.assert_get_cn_batches_works([
[
get_dummy_image(0),
get_dummy_image(0),
],
[
get_dummy_image(1),
get_dummy_image(2),
],
[
get_dummy_image(4),
get_dummy_image(5),
],
])
class TestProcessImagesPatchWorks(unittest.TestCase):
@unittest.mock.patch('modules.script_callbacks.on_script_unloaded')
def setUp(self, on_script_unloaded_mock):
self.on_script_unloaded_mock = on_script_unloaded_mock
self.p = unittest.mock.MagicMock()
assert scripts.scripts_txt2img is not None
self.p.scripts = scripts.scripts_txt2img
self.cn_script = controlnet.Script()
self.p.scripts.alwayson_scripts = [self.cn_script]
self.p.script_args = []
self.p.all_seeds = [0]
self.p.all_subseeds = [0]
self.old_model, shared.sd_model = shared.sd_model, unittest.mock.MagicMock()
self.batch_hijack_object = batch_hijack.BatchHijack()
self.callbacks_mock = unittest.mock.MagicMock()
self.batch_hijack_object.process_batch_callbacks.append(self.callbacks_mock.process)
self.batch_hijack_object.process_batch_each_callbacks.append(self.callbacks_mock.process_each)
self.batch_hijack_object.postprocess_batch_each_callbacks.insert(0, self.callbacks_mock.postprocess_each)
self.batch_hijack_object.postprocess_batch_callbacks.insert(0, self.callbacks_mock.postprocess)
self.batch_hijack_object.do_hijack()
shared.state.begin()
def tearDown(self):
shared.state.end()
self.batch_hijack_object.undo_hijack()
shared.sd_model = self.old_model
@unittest.mock.patch('modules.processing.__controlnet_original_process_images_inner')
def assert_process_images_hijack_called(self, process_images_mock, batch_count):
process_images_mock.return_value = processing.Processed(self.p, [get_dummy_image('output')])
with unittest.mock.patch.dict(shared.opts.data, {
'controlnet_show_batch_images_in_ui': True,
}):
res = processing.process_images_inner(self.p)
self.assertEqual(res, process_images_mock.return_value)
if batch_count > 0:
self.callbacks_mock.process.assert_called()
self.callbacks_mock.postprocess.assert_called()
else:
self.callbacks_mock.process.assert_not_called()
self.callbacks_mock.postprocess.assert_not_called()
self.assertEqual(self.callbacks_mock.process_each.call_count, batch_count)
self.assertEqual(self.callbacks_mock.postprocess_each.call_count, batch_count)
def test_process_images_no_units_forwards(self):
self.assert_process_images_hijack_called(batch_count=0)
def test_process_images__only_simple_units__forwards(self):
self.p.script_args = [
external_code.ControlNetUnit(image=get_dummy_image()),
external_code.ControlNetUnit(image=get_dummy_image()),
]
self.assert_process_images_hijack_called(batch_count=0)
def test_process_images__1_batch_1_unit__runs_1_batch(self):
self.p.script_args = [
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(),
],
),
]
self.assert_process_images_hijack_called(batch_count=1)
def test_process_images__2_batches_1_unit__runs_2_batches(self):
self.p.script_args = [
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(0),
get_dummy_image(1),
],
),
]
self.assert_process_images_hijack_called(batch_count=2)
def test_process_images__8_batches_1_unit__runs_8_batches(self):
batch_count = 8
self.p.script_args = [
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[get_dummy_image(i) for i in range(batch_count)]
),
]
self.assert_process_images_hijack_called(batch_count=batch_count)
def test_process_images__1_batch_2_units__runs_1_batch(self):
self.p.script_args = [
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[get_dummy_image(0)]
),
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[get_dummy_image(1)]
),
]
self.assert_process_images_hijack_called(batch_count=1)
def test_process_images__2_batches_2_units__runs_2_batches(self):
self.p.script_args = [
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(0),
get_dummy_image(1),
],
),
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(2),
get_dummy_image(3),
],
),
]
self.assert_process_images_hijack_called(batch_count=2)
def test_process_images__3_batches_2_mixed_units__runs_3_batches(self):
self.p.script_args = [
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.BATCH,
batch_images=[
get_dummy_image(0),
get_dummy_image(1),
get_dummy_image(2),
],
),
controlnet.UiControlNetUnit(
input_mode=batch_hijack.InputMode.SIMPLE,
image=get_dummy_image(3),
),
]
self.assert_process_images_hijack_called(batch_count=3)
def get_dummy_image(name: Any = 0):
return f'base64#{name}...'
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,184 @@
from typing import Any, Dict, List
import unittest
from PIL import Image
import numpy as np
import importlib
utils = importlib.import_module("extensions.sd-webui-controlnet.tests.utils", "utils")
from scripts import external_code, processor
from scripts.controlnet import prepare_mask, Script, set_numpy_seed
from modules import processing
class TestPrepareMask(unittest.TestCase):
def test_prepare_mask(self):
p = processing.StableDiffusionProcessing()
p.inpainting_mask_invert = True
p.mask_blur = 5
mask = Image.new("RGB", (10, 10), color="white")
processed_mask = prepare_mask(mask, p)
# Check that mask is correctly converted to grayscale
self.assertTrue(processed_mask.mode, "L")
# Check that mask colors are correctly inverted
self.assertEqual(
processed_mask.getpixel((0, 0)), 0
) # inverted white should be black
p.inpainting_mask_invert = False
processed_mask = prepare_mask(mask, p)
# Check that mask colors are not inverted when 'inpainting_mask_invert' is False
self.assertEqual(
processed_mask.getpixel((0, 0)), 255
) # white should remain white
p.mask_blur = 0
mask = Image.new("RGB", (10, 10), color="black")
processed_mask = prepare_mask(mask, p)
# Check that mask is not blurred when 'mask_blur' is 0
self.assertEqual(
processed_mask.getpixel((0, 0)), 0
) # black should remain black
class TestSetNumpySeed(unittest.TestCase):
def test_seed_subseed_minus_one(self):
p = processing.StableDiffusionProcessing()
p.seed = -1
p.subseed = -1
p.all_seeds = [123, 456]
expected_seed = (123 + 123) & 0xFFFFFFFF
self.assertEqual(set_numpy_seed(p), expected_seed)
def test_valid_seed_subseed(self):
p = processing.StableDiffusionProcessing()
p.seed = 50
p.subseed = 100
p.all_seeds = [123, 456]
expected_seed = (50 + 100) & 0xFFFFFFFF
self.assertEqual(set_numpy_seed(p), expected_seed)
def test_invalid_seed_subseed(self):
p = processing.StableDiffusionProcessing()
p.seed = "invalid"
p.subseed = 2.5
p.all_seeds = [123, 456]
self.assertEqual(set_numpy_seed(p), None)
def test_empty_all_seeds(self):
p = processing.StableDiffusionProcessing()
p.seed = -1
p.subseed = 2
p.all_seeds = []
self.assertEqual(set_numpy_seed(p), None)
def test_random_state_change(self):
p = processing.StableDiffusionProcessing()
p.seed = 50
p.subseed = 100
p.all_seeds = [123, 456]
expected_seed = (50 + 100) & 0xFFFFFFFF
np.random.seed(0) # set a known seed
before_random = np.random.randint(0, 1000) # get a random integer
seed = set_numpy_seed(p)
self.assertEqual(seed, expected_seed)
after_random = np.random.randint(0, 1000) # get another random integer
self.assertNotEqual(before_random, after_random)
class MockImg2ImgProcessing(processing.StableDiffusionProcessing):
"""Mock the Img2Img processing as the WebUI version have dependency on
`sd_model`."""
def __init__(self, init_images, resize_mode, *args, **kwargs):
super().__init__(*args, **kwargs)
self.init_images = init_images
self.resize_mode = resize_mode
class TestScript(unittest.TestCase):
sample_base64_image = (
"data:image/png;base64,"
"iVBORw0KGgoAAAANSUhEUgAAARMAAAC3CAIAAAC+MS2jAAAAqUlEQVR4nO3BAQ"
"0AAADCoPdPbQ8HFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAA/wZOlAAB5tU+nAAAAABJRU5ErkJggg=="
)
sample_np_image = np.array(
[[100, 200, 50], [150, 75, 225], [30, 120, 180]], dtype=np.uint8
)
def test_bound_check_params(self):
def param_required(module: str, param: str) -> bool:
configs = processor.preprocessor_sliders_config[module]
config_index = ("processor_res", "threshold_a", "threshold_b").index(param)
return config_index < len(configs) and configs[config_index] is not None
for module in processor.preprocessor_sliders_config.keys():
for param in ("processor_res", "threshold_a", "threshold_b"):
with self.subTest(param=param, module=module):
unit = external_code.ControlNetUnit(
module=module,
**{param: -100},
)
Script.bound_check_params(unit)
if param_required(module, param):
self.assertGreaterEqual(getattr(unit, param), 0)
else:
self.assertEqual(getattr(unit, param), -100)
def test_choose_input_image(self):
with self.subTest(name="no image"):
with self.assertRaises(ValueError):
Script.choose_input_image(
p=processing.StableDiffusionProcessing(),
unit=external_code.ControlNetUnit(),
idx=0,
)
with self.subTest(name="control net input"):
_, resize_mode = Script.choose_input_image(
p=MockImg2ImgProcessing(
init_images=[TestScript.sample_np_image],
resize_mode=external_code.ResizeMode.OUTER_FIT,
),
unit=external_code.ControlNetUnit(
image=TestScript.sample_base64_image,
module="none",
resize_mode=external_code.ResizeMode.INNER_FIT,
),
idx=0,
)
self.assertEqual(resize_mode, external_code.ResizeMode.INNER_FIT)
with self.subTest(name="A1111 input"):
_, resize_mode = Script.choose_input_image(
p=MockImg2ImgProcessing(
init_images=[TestScript.sample_np_image],
resize_mode=external_code.ResizeMode.OUTER_FIT,
),
unit=external_code.ControlNetUnit(
module="none",
resize_mode=external_code.ResizeMode.INNER_FIT,
),
idx=0,
)
self.assertEqual(resize_mode, external_code.ResizeMode.OUTER_FIT)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,67 @@
import importlib
utils = importlib.import_module("extensions.sd-webui-controlnet.tests.utils", "utils")
from scripts.global_state import select_control_type, ui_preprocessor_keys
from scripts.enums import StableDiffusionVersion
dummy_value = "dummy"
cn_models = {
"None": dummy_value,
"canny_sd15": dummy_value,
"canny_sdxl": dummy_value,
}
# Tests for the select_control_type function
class TestSelectControlType:
def test_all_control_type(self):
result = select_control_type("All", cn_models=cn_models)
assert result == (
[ui_preprocessor_keys, list(cn_models.keys()), "none", "None"]
), "Expected all preprocessors and models"
def test_sd_version(self):
(_, filtered_model_list, _, default_model) = select_control_type(
"Canny", sd_version=StableDiffusionVersion.UNKNOWN, cn_models=cn_models
)
assert filtered_model_list == [
"None",
"canny_sd15",
"canny_sdxl",
], "UNKNOWN sd version should match all models"
assert default_model == "canny_sd15"
(_, filtered_model_list, _, default_model) = select_control_type(
"Canny", sd_version=StableDiffusionVersion.SD1x, cn_models=cn_models
)
assert filtered_model_list == [
"None",
"canny_sd15",
], "sd1x version should only sd1x"
assert default_model == "canny_sd15"
(_, filtered_model_list, _, default_model) = select_control_type(
"Canny", sd_version=StableDiffusionVersion.SDXL, cn_models=cn_models
)
assert filtered_model_list == [
"None",
"canny_sdxl",
], "sdxl version should only sdxl"
assert default_model == "canny_sdxl"
def test_invert_preprocessor(self):
for control_type in ("Canny", "Lineart", "Scribble/Sketch", "MLSD"):
filtered_preprocessor_list, _, _, _ = select_control_type(
control_type, cn_models=cn_models
)
assert any(
"invert" in module.lower() for module in filtered_preprocessor_list
)
def test_no_module_available(self):
(_, filtered_model_list, _, default_model) = select_control_type(
"Depth", cn_models=cn_models
)
assert filtered_model_list == ["None"]
assert default_model == "None"

View File

@@ -0,0 +1,34 @@
import unittest
import importlib
utils = importlib.import_module("extensions.sd-webui-controlnet.tests.utils", "utils")
from scripts.infotext import parse_unit
from scripts.external_code import ControlNetUnit
class TestInfotext(unittest.TestCase):
def test_parsing(self):
infotext = (
"Module: inpaint_only+lama, Model: control_v11p_sd15_inpaint [ebff9138], Weight: 1, "
"Resize Mode: Resize and Fill, Low Vram: False, Guidance Start: 0, Guidance End: 1, "
"Pixel Perfect: True, Control Mode: Balanced, Hr Option: Both, Save Detected Map: True"
)
self.assertEqual(
vars(
ControlNetUnit(
module="inpaint_only+lama",
model="control_v11p_sd15_inpaint [ebff9138]",
weight=1,
resize_mode="Resize and Fill",
low_vram=False,
guidance_start=0,
guidance_end=1,
pixel_perfect=True,
control_mode="Balanced",
hr_option="Both",
save_detected_map=True,
)
),
vars(parse_unit(infotext)),
)

View File

@@ -0,0 +1,75 @@
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from scripts.utils import ndarray_lru_cache, get_unique_axis0
import unittest
import numpy as np
class TestNumpyLruCache(unittest.TestCase):
def setUp(self):
self.arr1 = np.array([1, 2, 3, 4, 5])
self.arr2 = np.array([1, 2, 3, 4, 5])
@ndarray_lru_cache(max_size=128)
def add_one(self, arr):
return arr + 1
def test_same_array(self):
# Test that the decorator works with numpy arrays.
result1 = self.add_one(self.arr1)
result2 = self.add_one(self.arr1)
# If caching is working correctly, these should be the same object.
self.assertIs(result1, result2)
def test_different_array_same_data(self):
# Test that the decorator works with different numpy arrays with the same data.
result1 = self.add_one(self.arr1)
result2 = self.add_one(self.arr2)
# If caching is working correctly, these should be the same object.
self.assertIs(result1, result2)
def test_cache_size(self):
# Test that the cache size limit is respected.
arrs = [np.array([i]) for i in range(150)]
# Add all arrays to the cache.
result1 = self.add_one(arrs[0])
for arr in arrs[1:]:
self.add_one(arr)
# Check that the first array is no longer in the cache.
result2 = self.add_one(arrs[0])
# If the cache size limit is working correctly, these should not be the same object.
self.assertIsNot(result1, result2)
def test_large_array(self):
# Create two large arrays with the same elements in the beginning and end, but one different element in the middle.
arr1 = np.ones(10000)
arr2 = np.ones(10000)
arr2[len(arr2)//2] = 0
result1 = self.add_one(arr1)
result2 = self.add_one(arr2)
# If hashing is working correctly, these should not be the same object because the input arrays are not equal.
self.assertIsNot(result1, result2)
class TestUniqueFunctions(unittest.TestCase):
def test_get_unique_axis0(self):
data = np.random.randint(0, 100, size=(100000, 3))
data = np.concatenate((data, data))
numpy_unique_res = np.unique(data, axis=0)
get_unique_axis0_res = get_unique_axis0(data)
self.assertEqual(np.array_equal(
np.sort(numpy_unique_res, axis=0), np.sort(get_unique_axis0_res, axis=0),
), True)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,170 @@
import unittest
import importlib
import numpy as np
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from copy import copy
from scripts import external_code
from scripts import controlnet
from modules import scripts, ui, shared
class TestExternalCodeWorking(unittest.TestCase):
max_models = 6
args_offset = 10
def setUp(self):
self.scripts = copy(scripts.scripts_txt2img)
self.scripts.initialize_scripts(False)
ui.create_ui()
self.cn_script = controlnet.Script()
self.cn_script.args_from = self.args_offset
self.cn_script.args_to = self.args_offset + self.max_models
self.scripts.alwayson_scripts = [self.cn_script]
self.script_args = [None] * self.cn_script.args_from
self.initial_max_models = shared.opts.data.get("control_net_unit_count", 3)
shared.opts.data.update(control_net_unit_count=self.max_models)
self.extra_models = 0
def tearDown(self):
shared.opts.data.update(control_net_unit_count=self.initial_max_models)
def get_expected_args_to(self):
args_len = max(self.max_models, len(self.cn_units))
return self.args_offset + args_len
def assert_update_in_place_ok(self):
external_code.update_cn_script_in_place(self.scripts, self.script_args, self.cn_units)
self.assertEqual(self.cn_script.args_to, self.get_expected_args_to())
def test_empty_resizes_min_args(self):
self.cn_units = []
self.assert_update_in_place_ok()
def test_empty_resizes_extra_args(self):
extra_models = 1
self.cn_units = [external_code.ControlNetUnit()] * (self.max_models + extra_models)
self.assert_update_in_place_ok()
class TestControlNetUnitConversion(unittest.TestCase):
def setUp(self):
self.dummy_image = 'base64...'
self.input = {}
self.expected = external_code.ControlNetUnit()
def assert_converts_to_expected(self):
self.assertEqual(vars(external_code.to_processing_unit(self.input)), vars(self.expected))
def test_empty_dict_works(self):
self.assert_converts_to_expected()
def test_image_works(self):
self.input = {
'image': self.dummy_image
}
self.expected = external_code.ControlNetUnit(image=self.dummy_image)
self.assert_converts_to_expected()
def test_image_alias_works(self):
self.input = {
'input_image': self.dummy_image
}
self.expected = external_code.ControlNetUnit(image=self.dummy_image)
self.assert_converts_to_expected()
def test_masked_image_works(self):
self.input = {
'image': self.dummy_image,
'mask': self.dummy_image,
}
self.expected = external_code.ControlNetUnit(image={'image': self.dummy_image, 'mask': self.dummy_image})
self.assert_converts_to_expected()
class TestControlNetUnitImageToDict(unittest.TestCase):
def setUp(self):
self.dummy_image = utils.readImage("test/test_files/img2img_basic.png")
self.input = external_code.ControlNetUnit()
self.expected_image = external_code.to_base64_nparray(self.dummy_image)
self.expected_mask = external_code.to_base64_nparray(self.dummy_image)
def assert_dict_is_valid(self):
actual_dict = controlnet.image_dict_from_any(self.input.image)
self.assertEqual(actual_dict['image'].tolist(), self.expected_image.tolist())
self.assertEqual(actual_dict['mask'].tolist(), self.expected_mask.tolist())
def test_none(self):
self.assertEqual(controlnet.image_dict_from_any(self.input.image), None)
def test_image_without_mask(self):
self.input.image = self.dummy_image
self.expected_mask = np.zeros_like(self.expected_image, dtype=np.uint8)
self.assert_dict_is_valid()
def test_masked_image_tuple(self):
self.input.image = (self.dummy_image, self.dummy_image,)
self.assert_dict_is_valid()
def test_masked_image_dict(self):
self.input.image = {'image': self.dummy_image, 'mask': self.dummy_image}
self.assert_dict_is_valid()
class TestPixelPerfectResolution(unittest.TestCase):
def test_outer_fit(self):
image = np.zeros((100, 100, 3))
target_H, target_W = 50, 100
resize_mode = external_code.ResizeMode.OUTER_FIT
result = external_code.pixel_perfect_resolution(image, target_H, target_W, resize_mode)
expected = 50 # manually computed expected result
self.assertEqual(result, expected)
def test_inner_fit(self):
image = np.zeros((100, 100, 3))
target_H, target_W = 50, 100
resize_mode = external_code.ResizeMode.INNER_FIT
result = external_code.pixel_perfect_resolution(image, target_H, target_W, resize_mode)
expected = 100 # manually computed expected result
self.assertEqual(result, expected)
class TestGetAllUnitsFrom(unittest.TestCase):
def test_none(self):
self.assertListEqual(external_code.get_all_units_from([None]), [])
def test_bool(self):
self.assertListEqual(external_code.get_all_units_from([True]), [])
def test_inheritance(self):
class Foo(external_code.ControlNetUnit):
def __init__(self):
super().__init__(self)
self.bar = 'a'
foo = Foo()
self.assertListEqual(external_code.get_all_units_from([foo]), [foo])
def test_dict(self):
units = external_code.get_all_units_from([{}])
self.assertGreater(len(units), 0)
self.assertIsInstance(units[0], external_code.ControlNetUnit)
def test_unitlike(self):
class Foo(object):
""" bar """
foo = Foo()
for key in vars(external_code.ControlNetUnit()).keys():
setattr(foo, key, True)
setattr(foo, 'bar', False)
self.assertListEqual(external_code.get_all_units_from([foo]), [foo])
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,24 @@
import unittest
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from scripts import external_code
class TestImportlibReload(unittest.TestCase):
def setUp(self):
self.ControlNetUnit = external_code.ControlNetUnit
def test_reload_does_not_redefine(self):
importlib.reload(external_code)
NewControlNetUnit = external_code.ControlNetUnit
self.assertEqual(self.ControlNetUnit, NewControlNetUnit)
def test_force_import_does_not_redefine(self):
external_code_copy = importlib.import_module('extensions.sd-webui-controlnet.scripts.external_code', 'external_code')
self.assertEqual(self.ControlNetUnit, external_code_copy.ControlNetUnit)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,34 @@
import unittest
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
from scripts import external_code
class TestGetAllUnitsFrom(unittest.TestCase):
def setUp(self):
self.control_unit = {
"module": "none",
"model": utils.get_model("canny"),
"image": utils.readImage("test/test_files/img2img_basic.png"),
"resize_mode": 1,
"low_vram": False,
"processor_res": 64,
"control_mode": external_code.ControlMode.BALANCED.value,
}
self.object_unit = external_code.ControlNetUnit(**self.control_unit)
def test_empty_converts(self):
script_args = []
units = external_code.get_all_units_from(script_args)
self.assertListEqual(units, [])
def test_object_forwards(self):
script_args = [self.object_unit]
units = external_code.get_all_units_from(script_args)
self.assertListEqual(units, [self.object_unit])
if __name__ == '__main__':
unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,67 @@
import os
import sys
import cv2
from base64 import b64encode
from pathlib import Path
import requests
BASE_URL = "http://localhost:7860"
# setup_test_env
os.environ['IGNORE_CMD_ARGS_ERRORS'] = 'True'
file_path = Path(__file__).resolve()
ext_root = file_path.parent.parent
a1111_root = ext_root.parent.parent
for p in (ext_root, a1111_root):
if p not in sys.path:
sys.path.append(str(p))
# Initialize A1111
from modules import initialize
initialize.imports()
initialize.initialize()
from scripts.enums import StableDiffusionVersion
def readImage(path):
img = cv2.imread(path)
retval, buffer = cv2.imencode('.jpg', img)
b64img = b64encode(buffer).decode("utf-8")
return b64img
def get_model(model_name: str, sd_version: StableDiffusionVersion = StableDiffusionVersion.SD1x) -> str:
""" Find an available model with specified model name and sd_version. """
if model_name.lower() == "none":
return "None"
r = requests.get(BASE_URL+"/controlnet/model_list")
result = r.json()
if "model_list" not in result:
raise ValueError("No model available")
candidates = [
model
for model in result["model_list"]
if (
model_name.lower() in model.lower() and
StableDiffusionVersion.detect_from_model_name(model) == sd_version
)
]
if not candidates:
raise ValueError("No suitable model available")
return candidates[0]
def get_modules():
return requests.get(f"{BASE_URL}/controlnet/module_list").json()
def detect(json):
return requests.post(BASE_URL+"/controlnet/detect", json=json)

View File

@@ -0,0 +1,60 @@
{
"version": "ap10k",
"animals": [
[
450.2489471435547,
131.68504521623254,
1.0,
392.43172235786915,
129.75780439004302,
1.0,
422.3039551638067,
170.2298617400229,
1.0,
424.2311959899962,
254.06483767926693,
1.0,
460.84877168759704,
416.9166874922812,
0.7048550844192505,
498.42996779829264,
295.50051544234157,
0.742408812046051,
513.8478944078088,
374.5173893161118,
0.763853132724762,
512.884273994714,
438.1163365803659,
1.0,
372.1956936828792,
301.2822379209101,
0.7799525856971741,
384.7227590531111,
381.2627322077751,
0.8117924928665161,
381.8318978138268,
442.9344386458397,
1.0,
553.3563313446939,
327.2999890744686,
0.7031180262565613,
555.2835721708834,
375.48100972920656,
0.6529693603515625,
562.0289150625467,
420.77116914466023,
0.8226040601730347,
409.7768897935748,
359.09946270659566,
0.3695080578327179,
436.75826136022806,
414.0258262529969,
0.6621587872505188,
428.08567764237523,
422.69840997084975,
0.552909255027771
]
],
"canvas_height": 512,
"canvas_width": 960
}

View File

@@ -0,0 +1,24 @@
import unittest
import importlib
import requests
utils = importlib.import_module(
'extensions.sd-webui-controlnet.tests.utils', 'utils')
from scripts.processor import preprocessor_filters
class TestControlTypes(unittest.TestCase):
def test_fetching_control_types(self):
response = requests.get(utils.BASE_URL + "/controlnet/control_types")
self.assertEqual(response.status_code, 200)
result = response.json()
self.assertIn('control_types', result)
for control_type in preprocessor_filters:
self.assertIn(control_type, result['control_types'])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,47 @@
import requests
import unittest
import importlib
utils = importlib.import_module(
'extensions.sd-webui-controlnet.tests.utils', 'utils')
class TestDetectEndpointWorking(unittest.TestCase):
def setUp(self):
self.base_detect_args = {
"controlnet_module": "canny",
"controlnet_input_images": [utils.readImage("test/test_files/img2img_basic.png")],
"controlnet_processor_res": 512,
"controlnet_threshold_a": 0,
"controlnet_threshold_b": 0,
}
def test_detect_with_invalid_module_performed(self):
detect_args = self.base_detect_args.copy()
detect_args.update({
"controlnet_module": "INVALID",
})
self.assertEqual(utils.detect(detect_args).status_code, 422)
def test_detect_with_no_input_images_performed(self):
detect_args = self.base_detect_args.copy()
detect_args.update({
"controlnet_input_images": [],
})
self.assertEqual(utils.detect(detect_args).status_code, 422)
def test_detect_with_valid_args_performed(self):
detect_args = self.base_detect_args
response = utils.detect(detect_args)
self.assertEqual(response.status_code, 200)
def test_detect_invert(self):
detect_args = self.base_detect_args.copy()
detect_args["controlnet_module"] = "invert"
response = utils.detect(detect_args)
self.assertEqual(response.status_code, 200)
self.assertNotEqual(response.json()['images'], [""])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,3 @@
# Full Coverage Tests
Tests that only run locally with all models available. Set environment variable
`CONTROLNET_TEST_FULL_COVERAGE` to any value to enable these tests.

View File

@@ -0,0 +1,66 @@
import unittest
import pytest
from typing import NamedTuple, Optional
from .template import (
sd_version,
StableDiffusionVersion,
is_full_coverage,
APITestTemplate,
living_room_img,
general_negative_prompt,
)
base_prompt = "A modern living room"
general_depth_modules = [
"depth",
"depth_leres",
"depth_leres++",
"depth_anything",
]
hand_refiner_module = "depth_hand_refiner"
general_depth_models = [
"control_sd15_depth_anything [48a4bc3a]",
"control_v11f1p_sd15_depth [cfd03158]",
"t2iadapter_depth_sd15v2 [3489cd37]",
]
hand_refiner_model = "control_sd15_inpaint_depth_hand_fp16 [09456e54]"
class TestDepthFullCoverage(unittest.TestCase):
def setUp(self):
if not is_full_coverage:
pytest.skip()
# TODO test SDXL.
if sd_version == StableDiffusionVersion.SDXL:
pytest.skip()
def test_depth(self):
for module in general_depth_modules:
for model in general_depth_models:
name = f"depth_txt2img_{module}_{model}"
with self.subTest(name=name):
self.assertTrue(
APITestTemplate(
name,
"txt2img",
payload_overrides={
"prompt": base_prompt,
"negative_prompt": general_negative_prompt,
"steps": 20,
"width": 768,
"height": 512,
},
unit_overrides={
"module": module,
"model": model,
"image": living_room_img,
},
).exec(result_only=False)
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,219 @@
import unittest
import pytest
from .template import (
is_full_coverage,
APITestTemplate,
girl_img,
mask_img,
mask_small_img,
)
class TestInpaintFullCoverage(unittest.TestCase):
def setUp(self):
if not is_full_coverage:
pytest.skip()
def test_inpaint(self):
for gen_type in ("img2img", "txt2img"):
if gen_type == "img2img":
payload = {
"init_images": [girl_img],
"mask": mask_img,
}
unit = {}
else:
payload = {}
unit = {
"image": {
"image": girl_img,
"mask": mask_img,
}
}
unit["model"] = "control_v11p_sd15_inpaint [ebff9138]"
for i_resize, resize_mode in enumerate(
("Just Resize", "Crop and Resize", "Resize and Fill")
):
# Gen 512x768(input image size) for resize.
if resize_mode == "Crop and Resize":
payload["height"] = 768
payload["width"] = 512
# Gen 512x512 for inner fit.
if resize_mode == "Crop and Resize":
payload["height"] = 512
payload["width"] = 512
# Gen 768x768 for outer fit.
if resize_mode == "Resize and Fill":
payload["height"] = 768
payload["width"] = 768
if gen_type == "img2img":
payload["resize_mode"] = i_resize
else:
unit["resize_mode"] = resize_mode
for module in ("inpaint_only", "inpaint", "inpaint_only+lama"):
unit["module"] = module
with self.subTest(
gen_type=gen_type,
resize_mode=resize_mode,
module=module,
):
self.assertTrue(
APITestTemplate(
f"{gen_type}_{resize_mode}_{module}",
gen_type,
payload_overrides=payload,
unit_overrides=unit,
).exec()
)
def test_inpaint_no_mask(self):
"""Inpaint should fail if no mask is provided. Output should not contain
ControlNet detected map."""
for gen_type in ("img2img", "txt2img"):
if gen_type == "img2img":
payload = {
"init_images": [girl_img],
}
unit = {}
else:
payload = {}
unit = {
"image": {
"image": girl_img,
}
}
unit["model"] = "control_v11p_sd15_inpaint [ebff9138]"
unit["module"] = "inpaint_only"
with self.subTest(gen_type=gen_type):
self.assertTrue(
APITestTemplate(
f"{gen_type}_no_mask_fail",
gen_type,
payload_overrides=payload,
unit_overrides=unit,
).exec()
)
def test_inpaint_double_mask(self):
"""When mask is provided for both a1111 img2img input and ControlNet
unit input, ControlNet input mask should be used."""
self.assertTrue(
APITestTemplate(
f"img2img_double_mask",
"img2img",
payload_overrides={
"init_images": [girl_img],
"mask": mask_img,
},
unit_overrides={
"image": {
"image": girl_img,
"mask": mask_small_img,
},
"model": "control_v11p_sd15_inpaint [ebff9138]",
"module": "inpaint",
},
).exec()
)
def test_img2img_mask_on_unit(self):
""" Usecase for inpaint_global_harmonious. """
self.assertTrue(
APITestTemplate(
f"img2img_mask_on_unit",
"img2img",
payload_overrides={
"init_images": [girl_img],
},
unit_overrides={
"image": {
"image": girl_img,
"mask": mask_small_img,
},
"model": "control_v11p_sd15_inpaint [ebff9138]",
"module": "inpaint",
},
).exec()
)
def test_outpaint_without_mask(self):
self.assertTrue(
APITestTemplate(
f"img2img_outpaint_without_mask",
"img2img",
payload_overrides={
"init_images": [girl_img],
"width": 768,
"height": 768,
"resize_mode": 2,
},
unit_overrides={
"model": "control_v11p_sd15_inpaint [ebff9138]",
"module": "inpaint_only+lama",
},
).exec()
)
self.assertTrue(
APITestTemplate(
f"txt2img_outpaint_without_mask",
"txt2img",
payload_overrides={
"width": 768,
"height": 768,
},
unit_overrides={
"model": "control_v11p_sd15_inpaint [ebff9138]",
"module": "inpaint_only+lama",
"image": {
"image": girl_img,
},
"resize_mode": 2,
},
).exec()
)
def test_inpaint_crop(self):
self.assertTrue(
APITestTemplate(
"img2img_inpaint_crop",
"img2img",
payload_overrides={
"init_images": [girl_img],
"inpaint_full_res": True,
"mask": mask_small_img,
},
unit_overrides={
"model": "control_v11p_sd15_canny [d14c016b]",
"module": "canny",
"inpaint_crop_input_image": True,
},
).exec()
)
self.assertTrue(
APITestTemplate(
"img2img_inpaint_no_crop",
"img2img",
payload_overrides={
"init_images": [girl_img],
"inpaint_full_res": True,
"mask": mask_small_img,
},
unit_overrides={
"model": "control_v11p_sd15_canny [d14c016b]",
"module": "canny",
"inpaint_crop_input_image": False,
},
).exec()
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,169 @@
import unittest
import pytest
from typing import NamedTuple, Optional
from .template import (
sd_version,
StableDiffusionVersion,
is_full_coverage,
APITestTemplate,
portrait_imgs,
realistic_girl_face_img,
general_negative_prompt,
)
class AdapterSetting(NamedTuple):
module: str
model: str
lora: Optional[str] = None
@property
def lora_prompt(self) -> str:
return f"<lora:{self.lora}:0.6>" if self.lora else ""
# Used to fix pose for better comparison between different settings.
openpose_unit = {
"module": "openpose",
"model": (
"control_v11p_sd15_openpose [cab727d4]"
if sd_version != StableDiffusionVersion.SDXL
else "kohya_controllllite_xl_openpose_anime [7e5349e5]"
),
"image": realistic_girl_face_img,
"weight": 0.8,
}
base_prompt = "1girl, simple background, (white_background: 1.2), portrait"
negative_prompts = {
"with_neg": general_negative_prompt,
"no_neg": "",
}
sd15_face_id = AdapterSetting(
"ip-adapter_face_id",
"ip-adapter-faceid_sd15 [0a1757e9]",
"ip-adapter-faceid_sd15_lora",
)
sd15_face_id_plus = AdapterSetting(
"ip-adapter_face_id_plus",
"ip-adapter-faceid-plus_sd15 [d86a490f]",
"ip-adapter-faceid-plus_sd15_lora",
)
sd15_face_id_plus_v2 = AdapterSetting(
"ip-adapter_face_id_plus",
"ip-adapter-faceid-plusv2_sd15 [6e14fc1a]",
"ip-adapter-faceid-plusv2_sd15_lora",
)
sd15_face_id_portrait = AdapterSetting(
"ip-adapter_face_id",
"ip-adapter-faceid-portrait_sd15 [b2609049]",
)
sdxl_face_id = AdapterSetting(
"ip-adapter_face_id",
"ip-adapter-faceid_sdxl [59ee31a3]",
"ip-adapter-faceid_sdxl_lora",
)
class TestIPAdapterFullCoverage(unittest.TestCase):
def setUp(self):
if not is_full_coverage:
pytest.skip()
if sd_version == StableDiffusionVersion.SDXL:
self.settings = [sdxl_face_id]
else:
self.settings = [
sd15_face_id,
sd15_face_id_plus,
sd15_face_id_plus_v2,
sd15_face_id_portrait,
]
def test_face_id(self):
for s in self.settings:
for n, negative_prompt in negative_prompts.items():
name = f"{s}_{n}"
with self.subTest(name=name):
self.assertTrue(
APITestTemplate(
name,
"txt2img",
payload_overrides={
"prompt": f"{base_prompt},{s.lora_prompt}",
"negative_prompt": negative_prompt,
"steps": 20,
"width": 512,
"height": 512,
},
unit_overrides=[
{
"module": s.module,
"model": s.model,
"image": realistic_girl_face_img,
},
openpose_unit,
],
).exec()
)
def test_face_id_multi_inputs(self):
for s in self.settings:
for n, negative_prompt in negative_prompts.items():
name = f"multi_inputs_{s}_{n}"
with self.subTest(name=name):
self.assertTrue(
APITestTemplate(
name=name,
gen_type="txt2img",
payload_overrides={
"prompt": f"{base_prompt}, {s.lora_prompt}",
"negative_prompt": negative_prompt,
"steps": 20,
"width": 512,
"height": 512,
},
unit_overrides=[openpose_unit]
+ [
{
"image": img,
"module": s.module,
"model": s.model,
"weight": 1 / len(portrait_imgs),
}
for img in portrait_imgs
],
).exec()
)
def test_face_id_real_multi_inputs(self):
for s in (sd15_face_id, sd15_face_id_portrait):
for n, negative_prompt in negative_prompts.items():
name = f"real_multi_{s}_{n}"
with self.subTest(name=name):
self.assertTrue(
APITestTemplate(
name=name,
gen_type="txt2img",
payload_overrides={
"prompt": f"{base_prompt}, {s.lora_prompt}",
"negative_prompt": negative_prompt,
"steps": 20,
"width": 512,
"height": 512,
},
unit_overrides=[
openpose_unit,
{
"image": [{"image": img} for img in portrait_imgs],
"module": s.module,
"model": s.model,
},
],
).exec()
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,265 @@
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)
return similar
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,
}

View File

@@ -0,0 +1,99 @@
import os
import unittest
import importlib
utils = importlib.import_module('extensions.sd-webui-controlnet.tests.utils', 'utils')
import requests
from scripts.enums import StableDiffusionVersion
class TestImg2ImgWorkingBase(unittest.TestCase):
def setUp(self):
sd_version = StableDiffusionVersion(int(
os.environ.get("CONTROLNET_TEST_SD_VERSION", StableDiffusionVersion.SD1x.value)))
self.model = utils.get_model("canny", sd_version)
controlnet_unit = {
"module": "none",
"model": self.model,
"weight": 1.0,
"input_image": utils.readImage("test/test_files/img2img_basic.png"),
"mask": utils.readImage("test/test_files/img2img_basic.png"),
"resize_mode": 1,
"lowvram": False,
"processor_res": 64,
"threshold_a": 64,
"threshold_b": 64,
"guidance_start": 0.0,
"guidance_end": 1.0,
"control_mode": 0,
}
setup_args = {"alwayson_scripts":{"ControlNet":{"args": ([controlnet_unit] * getattr(self, 'units_count', 1))}}}
self.setup_route(setup_args)
def setup_route(self, setup_args):
self.url_img2img = "http://localhost:7860/sdapi/v1/img2img"
self.simple_img2img = {
"init_images": [utils.readImage("test/test_files/img2img_basic.png")],
"resize_mode": 0,
"denoising_strength": 0.75,
"image_cfg_scale": 0,
"mask_blur": 4,
"inpainting_fill": 0,
"inpaint_full_res": True,
"inpaint_full_res_padding": 0,
"inpainting_mask_invert": 0,
"initial_noise_multiplier": 0,
"prompt": "example prompt",
"styles": [],
"seed": -1,
"subseed": -1,
"subseed_strength": 0,
"seed_resize_from_h": -1,
"seed_resize_from_w": -1,
"sampler_name": "Euler a",
"batch_size": 1,
"n_iter": 1,
"steps": 3,
"cfg_scale": 7,
"width": 64,
"height": 64,
"restore_faces": False,
"tiling": False,
"do_not_save_samples": False,
"do_not_save_grid": False,
"negative_prompt": "",
"eta": 0,
"s_churn": 0,
"s_tmax": 0,
"s_tmin": 0,
"s_noise": 1,
"override_settings": {},
"override_settings_restore_afterwards": True,
"sampler_index": "Euler a",
"include_init_images": False,
"send_images": True,
"save_images": False,
"alwayson_scripts": {}
}
self.simple_img2img.update(setup_args)
def assert_status_ok(self):
self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200)
def test_img2img_simple_performed(self):
self.assert_status_ok()
def test_img2img_alwayson_scripts_default_units(self):
self.units_count = 0
self.setUp()
self.assert_status_ok()
def test_img2img_default_params(self):
self.simple_img2img["alwayson_scripts"]["ControlNet"]["args"] = [{
"input_image": utils.readImage("test/test_files/img2img_basic.png"),
"model": self.model,
}]
self.assert_status_ok()
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,194 @@
{
"people": [
{
"pose_keypoints_2d": [
275.2506064884899,
196.32469357280343,
1,
303.3188016469506,
272.70982071889466,
1,
244.98447950024644,
292.09994638829477,
1,
236.38292745027104,
517.7037729015278,
1,
168.3984479500246,
418.0022744632577,
1,
412.90526047751445,
257.2121425016039,
1,
403.17894535813576,
510.14290732520925,
1,
294.43481869004336,
376.4781345848482,
1,
265.25747216047955,
562.5137576822758,
1,
0,
0,
0,
0,
0,
0,
359.0078938961762,
562.0711206495608,
1,
0,
0,
0,
0,
0,
0,
240.097671925037,
184.513914838073,
1,
308.33409148775263,
161.22089906296208,
1,
204.7558201874076,
213.05887067565308,
1,
366.61934701298674,
148.9278832878512,
1
],
"hand_right_keypoints_2d": [
168.39790150130915,
418.0005271461072,
1,
181.79401055357368,
399.3767976307846,
1,
184.8627576873498,
384.75709227716266,
1,
198.118414869015,
381.90007483819153,
1,
215.15048903799024,
386.8527024571639,
1,
180.1325238303079,
346.88417988394843,
1,
178.10795487568174,
321.1018085790239,
1,
190.70710955474613,
320.5669160600145,
1,
203.3062827659017,
325.5456061326966,
1,
172.3536896669116,
350.7525276652412,
1,
170.000622912838,
325.0336533262108,
1,
185.70972426755964,
323.476117950832,
1,
208.50724912413568,
333.4635128502484,
1,
163.50256975319706,
356.1325737292637,
1,
162.59147123450128,
335.0197021116338,
1,
183.9828354600188,
328.4553078224726,
1,
201.57171021013423,
337.94383954551654,
1,
152.9805889462092,
357.94403689402753,
1,
167.09651929267878,
341.7437601311937,
1,
180.9402668216081,
337.10207327446744,
1,
194.60028340222678,
343.0449246874465,
1
],
"hand_left_keypoints_2d": [
294.4393772120137,
376.476024395234,
1,
271.70933825161165,
384.48117305399165,
1,
257.2452829806548,
374.58948859472207,
1,
238.26122936397638,
375.2887100029166,
1,
219.89983184668415,
382.69322630254595,
1,
263.0323651487124,
320.1279349241104,
1,
246.94602107917282,
309.8099960810156,
1,
233.73717716804694,
314.1485136789638,
1,
224.27755744411303,
322.7892154545116,
1,
264.97558037166135,
334.6319791090978,
1,
254.35598193615226,
315.5629746257517,
1,
238.25810853722876,
321.2812182403252,
1,
223.57727818251382,
328.39525394113423,
1,
278.15831452661644,
337.1533682086847,
1,
265.12624946042416,
323.3619430418993,
1,
250.5919197031302,
325.30525908324694,
1,
235.2500911122877,
332.6721359855453,
1,
285.9427695830851,
341.50671458478496,
1,
274.50497773130155,
333.1376809270594,
1,
261.49768784257105,
328.7012203257942,
1,
248.90495501067193,
332.0535195828255,
1
]
}
],
"canvas_width": 512,
"canvas_height": 512
}

View File

@@ -0,0 +1,51 @@
import requests
import unittest
import importlib
import json
from pathlib import Path
utils = importlib.import_module("extensions.sd-webui-controlnet.tests.utils", "utils")
def render(poses):
return requests.post(
utils.BASE_URL + "/controlnet/render_openpose_json", json=poses
).json()
with open(Path(__file__).parent / "pose.json", "r") as f:
pose = json.load(f)
with open(Path(__file__).parent / "animal_pose.json", "r") as f:
animal_pose = json.load(f)
class TestDetectEndpointWorking(unittest.TestCase):
def test_render_single(self):
res = render([pose])
self.assertEqual(res["info"], "Success")
self.assertEqual(len(res["images"]), 1)
def test_render_multiple(self):
res = render([pose, pose])
self.assertEqual(res["info"], "Success")
self.assertEqual(len(res["images"]), 2)
def test_render_no_pose(self):
res = render([])
self.assertNotEqual(res["info"], "Success")
def test_render_invalid_pose(self):
res = render([{"foo": 10, "bar": 100}])
self.assertNotIn("info", res)
self.assertNotIn("images", res)
def test_render_animals(self):
res = render([animal_pose])
self.assertEqual(res["info"], "Success")
self.assertEqual(len(res["images"]), 1)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,286 @@
import os
import unittest
import requests
import importlib
utils = importlib.import_module("extensions.sd-webui-controlnet.tests.utils", "utils")
from scripts.enums import StableDiffusionVersion
from modules import shared
class TestAlwaysonTxt2ImgWorking(unittest.TestCase):
def setUp(self):
self.sd_version = StableDiffusionVersion(
int(
os.environ.get(
"CONTROLNET_TEST_SD_VERSION", StableDiffusionVersion.SD1x.value
)
)
)
self.model = utils.get_model("canny", self.sd_version)
controlnet_unit = {
"enabled": True,
"module": "none",
"model": self.model,
"weight": 1.0,
"image": utils.readImage("test/test_files/img2img_basic.png"),
"mask": utils.readImage("test/test_files/img2img_basic.png"),
"resize_mode": 1,
"lowvram": False,
"processor_res": 64,
"threshold_a": 64,
"threshold_b": 64,
"guidance_start": 0.0,
"guidance_end": 1.0,
"control_mode": 0,
"pixel_perfect": False,
}
setup_args = [controlnet_unit] * getattr(self, "units_count", 1)
self.setup_route(setup_args)
def setup_route(self, setup_args):
self.url_txt2img = "http://localhost:7860/sdapi/v1/txt2img"
self.simple_txt2img = {
"enable_hr": False,
"denoising_strength": 0,
"firstphase_width": 0,
"firstphase_height": 0,
"prompt": "example prompt",
"styles": [],
"seed": -1,
"subseed": -1,
"subseed_strength": 0,
"seed_resize_from_h": -1,
"seed_resize_from_w": -1,
"batch_size": 1,
"n_iter": 1,
"steps": 3,
"cfg_scale": 7,
"width": 64,
"height": 64,
"restore_faces": False,
"tiling": False,
"negative_prompt": "",
"eta": 0,
"s_churn": 0,
"s_tmax": 0,
"s_tmin": 0,
"s_noise": 1,
"sampler_index": "Euler a",
"alwayson_scripts": {},
}
self.setup_controlnet_params(setup_args)
def setup_controlnet_params(self, setup_args):
self.simple_txt2img["alwayson_scripts"]["ControlNet"] = {"args": setup_args}
def assert_status_ok(self, msg=None, expected_image_num=None):
msg = ("" if msg is None else msg) + f"\nPayload:\n{self.simple_txt2img}"
resp = requests.post(self.url_txt2img, json=self.simple_txt2img)
self.assertEqual(resp.status_code, 200, msg)
# Note: Exception/error in ControlNet code likely will cause hook failure, which further leads
# to detected map not being appended at the end of response image array.
data = resp.json()
if expected_image_num is None:
expected_image_num = self.simple_txt2img["n_iter"] * self.simple_txt2img[
"batch_size"
] + min(
sum(
[
unit.get("save_detected_map", True)
for unit in self.simple_txt2img["alwayson_scripts"]["ControlNet"][
"args"
]
]
),
shared.opts.data.get("control_net_unit_count", 3),
)
self.assertEqual(len(data["images"]), expected_image_num, msg)
def test_txt2img_simple_performed(self):
self.assert_status_ok()
def test_txt2img_alwayson_scripts_default_units(self):
self.units_count = 0
self.setUp()
self.assert_status_ok()
def test_txt2img_multiple_batches_performed(self):
self.simple_txt2img["n_iter"] = 2
self.assert_status_ok()
def test_txt2img_batch_performed(self):
self.simple_txt2img["batch_size"] = 2
self.assert_status_ok()
def test_txt2img_2_units(self):
self.units_count = 2
self.setUp()
self.assert_status_ok()
def test_txt2img_8_units(self):
self.units_count = 8
self.setUp()
self.assert_status_ok()
def test_txt2img_default_params(self):
self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"] = [
{
"input_image": utils.readImage("test/test_files/img2img_basic.png"),
"model": self.model,
}
]
self.assert_status_ok()
def test_call_with_preprocessors(self):
available_modules = utils.get_modules()
available_modules_list = available_modules.get("module_list", [])
available_modules_detail = available_modules.get("module_detail", {})
for module in ["depth", "openpose_full"]:
assert module in available_modules_list, f"Failed to find {module}."
assert (
module in available_modules_detail
), f"Failed to find {module}'s detail."
with self.subTest(module=module):
self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"] = [
{
"input_image": utils.readImage(
"test/test_files/img2img_basic.png"
),
"model": self.model,
"module": module,
}
]
self.assert_status_ok(f"Running preprocessor module: {module}")
def test_call_invalid_params(self):
for param in ("processor_res", "threshold_a", "threshold_b"):
with self.subTest(param=param):
self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"] = [
{
"input_image": utils.readImage(
"test/test_files/img2img_basic.png"
),
"model": self.model,
param: -1,
}
]
self.assert_status_ok(f"Run with {param} = -1.")
def test_save_detected_map(self):
for save_map in (True, False):
with self.subTest(save_map=save_map):
self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"] = [
{
"input_image": utils.readImage(
"test/test_files/img2img_basic.png"
),
"model": self.model,
"module": "depth",
"save_detected_map": save_map,
}
]
resp = requests.post(self.url_txt2img, json=self.simple_txt2img).json()
self.assertEqual(2 if save_map else 1, len(resp["images"]))
def run_test_unit(
self, module: str, model: str, sd_version: StableDiffusionVersion
) -> None:
if self.sd_version != sd_version:
return
self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"] = [
{
"input_image": utils.readImage("test/test_files/img2img_basic.png"),
"model": utils.get_model(model, sd_version),
"module": module,
}
]
self.assert_status_ok()
def test_ip_adapter_face(self):
self.run_test_unit(
"ip-adapter_clip_sdxl_plus_vith",
"ip-adapter-plus-face_sdxl_vit-h",
StableDiffusionVersion.SDXL,
)
self.run_test_unit(
"ip-adapter_clip_sd15",
"ip-adapter-plus-face_sd15",
StableDiffusionVersion.SD1x,
)
def test_ip_adapter_fullface(self):
self.run_test_unit(
"ip-adapter_clip_sd15",
"ip-adapter-full-face_sd15",
StableDiffusionVersion.SD1x,
)
def test_control_lora(self):
self.run_test_unit("canny", "sai_xl_canny_128lora", StableDiffusionVersion.SDXL)
self.run_test_unit("canny", "control_lora_rank128_v11p_sd15_canny", StableDiffusionVersion.SD1x)
def test_control_lllite(self):
self.run_test_unit(
"canny", "kohya_controllllite_xl_canny", StableDiffusionVersion.SDXL
)
def test_diffusers_controlnet(self):
self.run_test_unit(
"canny", "diffusers_xl_canny_small", StableDiffusionVersion.SDXL
)
def test_t2i_adapter(self):
self.run_test_unit(
"canny", "t2iadapter_canny_sd15v2", StableDiffusionVersion.SD1x
)
self.run_test_unit("canny", "t2i-adapter_xl_canny", StableDiffusionVersion.SDXL)
def test_reference(self):
self.run_test_unit("reference_only", "None", StableDiffusionVersion.SD1x)
self.run_test_unit("reference_only", "None", StableDiffusionVersion.SDXL)
def test_unrecognized_param(self):
unit = self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"][0]
unit["foo"] = True
unit["is_ui"] = False
self.assert_status_ok()
def test_default_model(self):
# Model "None" should be used when model is not specified in the payload.
self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"] = [
{
"input_image": utils.readImage("test/test_files/img2img_basic.png"),
"module": "reference_only",
}
]
self.assert_status_ok()
def test_advanced_weighting(self):
unit = self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"][0]
unit["advanced_weighting"] = [0.75] * self.sd_version.controlnet_layer_num()
self.assert_status_ok()
def test_hr_option(self):
# In non-hr run, hr_option should be ignored.
unit = self.simple_txt2img["alwayson_scripts"]["ControlNet"]["args"][0]
unit["hr_option"] = "High res only"
self.assert_status_ok(expected_image_num=2)
# Hr run.
self.simple_txt2img["enable_hr"] = True
self.assert_status_ok(expected_image_num=3)
self.simple_txt2img["enable_hr"] = True
unit["hr_option"] = "HiResFixOption.BOTH"
self.assert_status_ok(expected_image_num=3)
if __name__ == "__main__":
unittest.main()