diff options
-rw-r--r-- | .github/workflows/run_tests.yaml | 31 | ||||
-rw-r--r-- | launch.py | 38 | ||||
-rw-r--r-- | modules/api/api.py | 2 | ||||
-rw-r--r-- | modules/extras.py | 40 | ||||
-rw-r--r-- | modules/generation_parameters_copypaste.py | 1 | ||||
-rw-r--r-- | modules/images.py | 38 | ||||
-rw-r--r-- | modules/ngrok.py | 4 | ||||
-rw-r--r-- | modules/sd_samplers.py | 2 | ||||
-rw-r--r-- | modules/ui.py | 1 | ||||
-rw-r--r-- | test/advanced_features/__init__.py | 0 | ||||
-rw-r--r-- | test/advanced_features/extras_test.py (renamed from test/extras_test.py) | 4 | ||||
-rw-r--r-- | test/advanced_features/txt2img_test.py | 47 | ||||
-rw-r--r-- | test/basic_features/__init__.py | 0 | ||||
-rw-r--r-- | test/basic_features/img2img_test.py (renamed from test/img2img_test.py) | 4 | ||||
-rw-r--r-- | test/basic_features/txt2img_test.py (renamed from test/txt2img_test.py) | 12 | ||||
-rw-r--r-- | test/basic_features/utils_test.py (renamed from test/utils_test.py) | 20 | ||||
-rw-r--r-- | test/server_poll.py | 13 | ||||
-rw-r--r-- | test/test_files/empty.pt | bin | 0 -> 431 bytes |
18 files changed, 173 insertions, 84 deletions
diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml new file mode 100644 index 00000000..49dc92bd --- /dev/null +++ b/.github/workflows/run_tests.yaml @@ -0,0 +1,31 @@ +name: Run basic features tests on CPU with empty SD model + +on: + - push + - pull_request + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: 3.10.6 + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: ${{ runner.os }}-pip- + - name: Run tests + run: python launch.py --tests basic_features --no-half --disable-opt-split-attention --use-cpu all --skip-torch-cuda-test + - name: Upload main app stdout-stderr + uses: actions/upload-artifact@v3 + if: always() + with: + name: stdout-stderr + path: | + test/stdout.txt + test/stderr.txt @@ -17,6 +17,19 @@ def extract_arg(args, name): return [x for x in args if x != name], name in args
+def extract_opt(args, name):
+ opt = None
+ is_present = False
+ if name in args:
+ is_present = True
+ idx = args.index(name)
+ del args[idx]
+ if idx < len(args) and args[idx][0] != "-":
+ opt = args[idx]
+ del args[idx]
+ return args, is_present, opt
+
+
def run(command, desc=None, errdesc=None, custom_env=None):
if desc is not None:
print(desc)
@@ -151,12 +164,11 @@ def prepare_enviroment(): blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
sys.argv += shlex.split(commandline_args)
- test_argv = [x for x in sys.argv if x != '--tests']
sys.argv, skip_torch_cuda_test = extract_arg(sys.argv, '--skip-torch-cuda-test')
sys.argv, reinstall_xformers = extract_arg(sys.argv, '--reinstall-xformers')
sys.argv, update_check = extract_arg(sys.argv, '--update-check')
- sys.argv, run_tests = extract_arg(sys.argv, '--tests')
+ sys.argv, run_tests, test_dir = extract_opt(sys.argv, '--tests')
xformers = '--xformers' in sys.argv
ngrok = '--ngrok' in sys.argv
@@ -221,24 +233,30 @@ def prepare_enviroment(): exit(0)
if run_tests:
- tests(test_argv)
- exit(0)
+ exitcode = tests(test_dir)
+ exit(exitcode)
-def tests(argv):
- if "--api" not in argv:
- argv.append("--api")
+def tests(test_dir):
+ if "--api" not in sys.argv:
+ sys.argv.append("--api")
+ if "--ckpt" not in sys.argv:
+ sys.argv.append("--ckpt")
+ sys.argv.append("./test/test_files/empty.pt")
+ if "--skip-torch-cuda-test" not in sys.argv:
+ sys.argv.append("--skip-torch-cuda-test")
- print(f"Launching Web UI in another process for testing with arguments: {' '.join(argv[1:])}")
+ print(f"Launching Web UI in another process for testing with arguments: {' '.join(sys.argv[1:])}")
with open('test/stdout.txt', "w", encoding="utf8") as stdout, open('test/stderr.txt', "w", encoding="utf8") as stderr:
- proc = subprocess.Popen([sys.executable, *argv], stdout=stdout, stderr=stderr)
+ proc = subprocess.Popen([sys.executable, *sys.argv], stdout=stdout, stderr=stderr)
import test.server_poll
- test.server_poll.run_tests()
+ exitcode = test.server_poll.run_tests(proc, test_dir)
print(f"Stopping Web UI process with id {proc.pid}")
proc.kill()
+ return exitcode
def start():
diff --git a/modules/api/api.py b/modules/api/api.py index 2f450fc4..1de3f98f 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -314,7 +314,7 @@ class Api: styleList = [] for k in shared.prompt_styles.styles: style = shared.prompt_styles.styles[k] - styleList.append({"name":style[0], "prompt": style[1], "negative_prompr": style[2]}) + styleList.append({"name":style[0], "prompt": style[1], "negative_prompt": style[2]}) return styleList diff --git a/modules/extras.py b/modules/extras.py index 0057bf9c..6021a024 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -1,6 +1,8 @@ from __future__ import annotations
import math
import os
+import sys
+import traceback
import numpy as np
from PIL import Image
@@ -12,7 +14,7 @@ from typing import Callable, List, OrderedDict, Tuple from functools import partial
from dataclasses import dataclass
-from modules import processing, shared, images, devices, sd_models
+from modules import processing, shared, images, devices, sd_models, sd_samplers
from modules.shared import opts
import modules.gfpgan_model
from modules.ui import plaintext_to_html
@@ -22,7 +24,6 @@ import piexif.helper import gradio as gr
import safetensors.torch
-
class LruCache(OrderedDict):
@dataclass(frozen=True)
class Key:
@@ -214,39 +215,8 @@ def run_pnginfo(image): if image is None:
return '', '', ''
- items = image.info
- geninfo = ''
-
- if "exif" in image.info:
- exif = piexif.load(image.info["exif"])
- exif_comment = (exif or {}).get("Exif", {}).get(piexif.ExifIFD.UserComment, b'')
- try:
- exif_comment = piexif.helper.UserComment.load(exif_comment)
- except ValueError:
- exif_comment = exif_comment.decode('utf8', errors="ignore")
-
- items['exif comment'] = exif_comment
- geninfo = exif_comment
-
- for field in ['jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif',
- 'loop', 'background', 'timestamp', 'duration']:
- items.pop(field, None)
-
- geninfo = items.get('parameters', geninfo)
-
- # nai prompt
- if "Software" in items.keys() and items["Software"] == "NovelAI":
- import json
- json_info = json.loads(items["Comment"])
- geninfo = f'{items["Description"]}\r\nNegative prompt: {json_info["uc"]}\r\n'
- sampler = "Euler a"
- if json_info["sampler"] == "k_euler_ancestral":
- sampler = "Euler a"
- elif json_info["sampler"] == "k_euler":
- sampler = "Euler"
- model_hash = '925997e9' # assuming this is the correct model hash
- # not sure with noise and strength parameter
- geninfo += f'Steps: {json_info["steps"]}, Sampler: {sampler}, CFG scale: {json_info["scale"]}, Seed: {json_info["seed"]}, Size: {image.width}x{image.height}, Model hash: {model_hash}' # , Denoising strength: {json_info["noise"]}'
+ geninfo, items = images.read_info_from_image(image)
+ items = {**{'parameters': geninfo}, **items}
info = ''
for key, text in items.items():
diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 1408ea05..0973c695 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -75,6 +75,7 @@ def integrate_settings_paste_fields(component_dict): 'CLIP_stop_at_last_layers': 'Clip skip',
'inpainting_mask_weight': 'Conditional mask weight',
'sd_model_checkpoint': 'Model hash',
+ 'eta_noise_seed_delta': 'ENSD',
}
settings_paste_fields = [
(component_dict[k], lambda d, k=k, v=v: ui.apply_setting(k, d.get(v, None)))
diff --git a/modules/images.py b/modules/images.py index b968d6a6..08a72e67 100644 --- a/modules/images.py +++ b/modules/images.py @@ -15,6 +15,7 @@ import piexif.helper from PIL import Image, ImageFont, ImageDraw, PngImagePlugin
from fonts.ttf import Roboto
import string
+import json
from modules import sd_samplers, shared, script_callbacks
from modules.shared import opts, cmd_opts
@@ -553,10 +554,45 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i return fullfn, txt_fullfn
+def read_info_from_image(image):
+ items = image.info or {}
+
+ geninfo = items.pop('parameters', None)
+
+ if "exif" in items:
+ exif = piexif.load(items["exif"])
+ exif_comment = (exif or {}).get("Exif", {}).get(piexif.ExifIFD.UserComment, b'')
+ try:
+ exif_comment = piexif.helper.UserComment.load(exif_comment)
+ except ValueError:
+ exif_comment = exif_comment.decode('utf8', errors="ignore")
+
+ items['exif comment'] = exif_comment
+ geninfo = exif_comment
+
+ for field in ['jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif',
+ 'loop', 'background', 'timestamp', 'duration']:
+ items.pop(field, None)
+
+ if items.get("Software", None) == "NovelAI":
+ try:
+ json_info = json.loads(items["Comment"])
+ sampler = sd_samplers.samplers_map.get(json_info["sampler"], "Euler a")
+
+ geninfo = f"""{items["Description"]}
+Negative prompt: {json_info["uc"]}
+Steps: {json_info["steps"]}, Sampler: {sampler}, CFG scale: {json_info["scale"]}, Seed: {json_info["seed"]}, Size: {image.width}x{image.height}, Clip skip: 2, ENSD: 31337"""
+ except Exception:
+ print(f"Error parsing NovelAI iamge generation parameters:", file=sys.stderr)
+ print(traceback.format_exc(), file=sys.stderr)
+
+ return geninfo, items
+
+
def image_data(data):
try:
image = Image.open(io.BytesIO(data))
- textinfo = image.text["parameters"]
+ textinfo, _ = read_info_from_image(image)
return textinfo, None
except Exception:
pass
diff --git a/modules/ngrok.py b/modules/ngrok.py index 10d2179f..64c9a3c2 100644 --- a/modules/ngrok.py +++ b/modules/ngrok.py @@ -15,9 +15,9 @@ def connect(token, port, region): ) try: if account == None: - public_url = ngrok.connect(port, pyngrok_config=config).public_url + public_url = ngrok.connect(port, pyngrok_config=config, bind_tls=True).public_url else: - public_url = ngrok.connect(port, pyngrok_config=config, auth=account).public_url + public_url = ngrok.connect(port, pyngrok_config=config, bind_tls=True, auth=account).public_url except exception.PyngrokNgrokError: print(f'Invalid ngrok authtoken, ngrok connection aborted.\n' f'Your token: {token}, get the right one on https://dashboard.ngrok.com/get-started/your-authtoken') diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 2ca17d8b..5fefb227 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -18,7 +18,7 @@ from modules.script_callbacks import CFGDenoiserParams, cfg_denoiser_callback SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options'])
samplers_k_diffusion = [
- ('Euler a', 'sample_euler_ancestral', ['k_euler_a'], {}),
+ ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {}),
('Euler', 'sample_euler', ['k_euler'], {}),
('LMS', 'sample_lms', ['k_lms'], {}),
('Heun', 'sample_heun', ['k_heun'], {}),
diff --git a/modules/ui.py b/modules/ui.py index aa13978d..446bee40 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1044,6 +1044,7 @@ def create_ui(wrap_gradio_gpu_call): (seed_resize_from_w, "Seed resize from-1"),
(seed_resize_from_h, "Seed resize from-2"),
(denoising_strength, "Denoising strength"),
+ (mask_blur, "Mask blur"),
*modules.scripts.scripts_img2img.infotext_fields
]
parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields)
diff --git a/test/advanced_features/__init__.py b/test/advanced_features/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/advanced_features/__init__.py diff --git a/test/extras_test.py b/test/advanced_features/extras_test.py index 9b8ce0f0..8763f8ed 100644 --- a/test/extras_test.py +++ b/test/advanced_features/extras_test.py @@ -11,8 +11,8 @@ class TestExtrasWorking(unittest.TestCase): "codeformer_visibility": 0, "codeformer_weight": 0, "upscaling_resize": 2, - "upscaling_resize_w": 512, - "upscaling_resize_h": 512, + "upscaling_resize_w": 128, + "upscaling_resize_h": 128, "upscaling_crop": True, "upscaler_1": "None", "upscaler_2": "None", diff --git a/test/advanced_features/txt2img_test.py b/test/advanced_features/txt2img_test.py new file mode 100644 index 00000000..36ed7b9a --- /dev/null +++ b/test/advanced_features/txt2img_test.py @@ -0,0 +1,47 @@ +import unittest +import requests + + +class TestTxt2ImgWorking(unittest.TestCase): + def setUp(self): + 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" + } + + def test_txt2img_with_restore_faces_performed(self): + self.simple_txt2img["restore_faces"] = True + self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) + + +class TestTxt2ImgCorrectness(unittest.TestCase): + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/test/basic_features/__init__.py b/test/basic_features/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/basic_features/__init__.py diff --git a/test/img2img_test.py b/test/basic_features/img2img_test.py index 012a9580..0a9c1e8a 100644 --- a/test/img2img_test.py +++ b/test/basic_features/img2img_test.py @@ -51,9 +51,5 @@ class TestImg2ImgWorking(unittest.TestCase): self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) -class TestImg2ImgCorrectness(unittest.TestCase): - pass - - if __name__ == "__main__": unittest.main() diff --git a/test/txt2img_test.py b/test/basic_features/txt2img_test.py index 1936e07e..1c2674b2 100644 --- a/test/txt2img_test.py +++ b/test/basic_features/txt2img_test.py @@ -49,26 +49,20 @@ class TestTxt2ImgWorking(unittest.TestCase): self.simple_txt2img["enable_hr"] = True self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - def test_txt2img_with_restore_faces_performed(self): - self.simple_txt2img["restore_faces"] = True - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_with_tiling_faces_performed(self): + def test_txt2img_with_tiling_performed(self): self.simple_txt2img["tiling"] = True self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) def test_txt2img_with_vanilla_sampler_performed(self): self.simple_txt2img["sampler_index"] = "PLMS" self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) + self.simple_txt2img["sampler_index"] = "DDIM" + self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) def test_txt2img_multiple_batches_performed(self): self.simple_txt2img["n_iter"] = 2 self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) -class TestTxt2ImgCorrectness(unittest.TestCase): - pass - - if __name__ == "__main__": unittest.main() diff --git a/test/utils_test.py b/test/basic_features/utils_test.py index 65d3d177..765470c9 100644 --- a/test/utils_test.py +++ b/test/basic_features/utils_test.py @@ -18,20 +18,6 @@ class UtilsTests(unittest.TestCase): def test_options_get(self): self.assertEqual(requests.get(self.url_options).status_code, 200) - def test_options_write(self): - response = requests.get(self.url_options) - self.assertEqual(response.status_code, 200) - - pre_value = response.json()["send_seed"] - - self.assertEqual(requests.post(self.url_options, json={"send_seed":not pre_value}).status_code, 200) - - response = requests.get(self.url_options) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["send_seed"], not pre_value) - - requests.post(self.url_options, json={"send_seed": pre_value}) - def test_cmd_flags(self): self.assertEqual(requests.get(self.url_cmd_flags).status_code, 200) @@ -60,4 +46,8 @@ class UtilsTests(unittest.TestCase): self.assertEqual(requests.get(self.url_artist_categories).status_code, 200) def test_artists(self): - self.assertEqual(requests.get(self.url_artists).status_code, 200)
\ No newline at end of file + self.assertEqual(requests.get(self.url_artists).status_code, 200) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/server_poll.py b/test/server_poll.py index eeefb7eb..d4df697b 100644 --- a/test/server_poll.py +++ b/test/server_poll.py @@ -3,7 +3,7 @@ import requests import time -def run_tests(): +def run_tests(proc, test_dir): timeout_threshold = 240 start_time = time.time() while time.time()-start_time < timeout_threshold: @@ -11,9 +11,14 @@ def run_tests(): requests.head("http://localhost:7860/") break except requests.exceptions.ConnectionError: - pass - if time.time()-start_time < timeout_threshold: - suite = unittest.TestLoader().discover('', pattern='*_test.py') + if proc.poll() is not None: + break + if proc.poll() is None: + if test_dir is None: + test_dir = "" + suite = unittest.TestLoader().discover(test_dir, pattern="*_test.py", top_level_dir="test") result = unittest.TextTestRunner(verbosity=2).run(suite) + return len(result.failures) + len(result.errors) else: print("Launch unsuccessful") + return 1 diff --git a/test/test_files/empty.pt b/test/test_files/empty.pt Binary files differnew file mode 100644 index 00000000..c6ac59eb --- /dev/null +++ b/test/test_files/empty.pt |