upload a cn
47
extensions-builtin/sd_forge_controlnet/tests/README.md
Normal 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
|
||||
```
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
@@ -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)),
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
BIN
extensions-builtin/sd_forge_controlnet/tests/images/1girl.png
Normal file
|
After Width: | Height: | Size: 482 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 78 KiB |
BIN
extensions-builtin/sd_forge_controlnet/tests/images/mask.png
Normal file
|
After Width: | Height: | Size: 244 B |
|
After Width: | Height: | Size: 226 B |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
extensions-builtin/sd_forge_controlnet/tests/images/ski.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
extensions-builtin/sd_forge_controlnet/tests/images/woman.jpeg
Normal file
|
After Width: | Height: | Size: 44 KiB |
67
extensions-builtin/sd_forge_controlnet/tests/utils.py
Normal 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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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()
|
||||
194
extensions-builtin/sd_forge_controlnet/tests/web_api/pose.json
Normal 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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||