diff --git a/.dev_scripts/test_regression_txt2img_dream_v1_4.sh b/.dev_scripts/test_regression_txt2img_dream_v1_4.sh index 11cbf8f14b..9326d3c311 100644 --- a/.dev_scripts/test_regression_txt2img_dream_v1_4.sh +++ b/.dev_scripts/test_regression_txt2img_dream_v1_4.sh @@ -5,8 +5,7 @@ SAMPLES_DIR=${OUT_DIR} python scripts/dream.py \ --from_file ${PROMPT_FILE} \ --outdir ${OUT_DIR} \ - --sampler plms \ - --full_precision + --sampler plms # original output by CompVis/stable-diffusion IMAGE1=".dev_scripts/images/v1_4_astronaut_rides_horse_plms_step50_seed42.png" diff --git a/.github/workflows/mkdocs-flow.yml b/.github/workflows/mkdocs-flow.yml index c4a6365304..49a9cbd423 100644 --- a/.github/workflows/mkdocs-flow.yml +++ b/.github/workflows/mkdocs-flow.yml @@ -12,7 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Build uses: Tiryoh/actions-mkdocs@v0 with: diff --git a/.github/workflows/test-dream-conda.yml b/.github/workflows/test-dream-conda.yml index 3bd9b24582..6c51ebe718 100644 --- a/.github/workflows/test-dream-conda.yml +++ b/.github/workflows/test-dream-conda.yml @@ -85,9 +85,9 @@ jobs: fi # Utterly hacky, but I don't know how else to do this if [[ ${{ github.ref }} == 'refs/heads/master' ]]; then - time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/dream.py --from_file tests/preflight_prompts.txt --full_precision + time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/dream.py --from_file tests/preflight_prompts.txt elif [[ ${{ github.ref }} == 'refs/heads/development' ]]; then - time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/dream.py --from_file tests/dev_prompts.txt --full_precision + time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/dream.py --from_file tests/dev_prompts.txt fi mkdir -p outputs/img-samples - name: Archive results diff --git a/.gitignore b/.gitignore index 6faecf6163..4c0ec37174 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ outputs/ models/ldm/stable-diffusion-v1/model.ckpt ldm/restoration/codeformer/weights +# ignore the Anaconda/Miniconda installer used while building Docker image +anaconda.sh + # ignore a directory which serves as a place for initial images inputs/ diff --git a/.prettierrc.yaml b/.prettierrc.yaml index 68eae1ba15..ce4b99a07b 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -5,9 +5,9 @@ singleQuote: true quoteProps: as-needed embeddedLanguageFormatting: auto overrides: - - files: "*.md" + - files: '*.md' options: proseWrap: always - printWidth: 100 + printWidth: 80 parser: markdown cursorOffset: -1 diff --git a/README.md b/README.md index 7b4ffa76a1..cf056fd6f4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
+

InvokeAI: A Stable Diffusion Toolkit

# Stable Diffusion Dream Script @@ -8,7 +8,7 @@

-# **Stable Diffusion Dream Script** +# **InvokeAI - A Stable Diffusion Toolkit** [![discord badge]][discord link] [![latest release badge]][latest release link] [![github stars badge]][github stars link] [![github forks badge]][github forks link] @@ -86,17 +86,14 @@ You wil need one of the following: - At least 6 GB of free disk space for the machine learning model, Python, and all its dependencies. -> Note -> -> If you have an Nvidia 10xx series card (e.g. the 1080ti), please run the dream script in -> full-precision mode as shown below. +#### Note -Similarly, specify full-precision mode on Apple M1 hardware. - -To run in full-precision mode, start `dream.py` with the `--full_precision` flag: +Precision is auto configured based on the device. If however you encounter +errors like 'expected type Float but found Half' or 'not implemented for Half' +you can try starting `dream.py` with the `--precision=float32` flag: ```bash -(ldm) ~/stable-diffusion$ python scripts/dream.py --full_precision +(ldm) ~/stable-diffusion$ python scripts/dream.py --precision=float32 ``` ### Features @@ -125,6 +122,11 @@ To run in full-precision mode, start `dream.py` with the `--full_precision` flag ### Latest Changes +- vNEXT (TODO 2022) + + - Deprecated `--full_precision` / `-F`. Simply omit it and `dream.py` will auto + configure. To switch away from auto use the new flag like `--precision=float32`. + - v1.14 (11 September 2022) - Memory optimizations for small-RAM cards. 512x512 now possible on 4 GB GPUs. diff --git a/backend/modules/parameters.py b/backend/modules/parameters.py index 95d07921ac..ec0cfe8272 100644 --- a/backend/modules/parameters.py +++ b/backend/modules/parameters.py @@ -2,14 +2,14 @@ from modules.parse_seed_weights import parse_seed_weights import argparse SAMPLER_CHOICES = [ - 'ddim', - 'k_dpm_2_a', - 'k_dpm_2', - 'k_euler_a', - 'k_euler', - 'k_heun', - 'k_lms', - 'plms', + "ddim", + "k_dpm_2_a", + "k_dpm_2", + "k_euler_a", + "k_euler", + "k_heun", + "k_lms", + "plms", ] @@ -20,194 +20,42 @@ def parameters_to_command(params): switches = list() - if 'prompt' in params: + if "prompt" in params: switches.append(f'"{params["prompt"]}"') - if 'steps' in params: + if "steps" in params: switches.append(f'-s {params["steps"]}') - if 'seed' in params: + if "seed" in params: switches.append(f'-S {params["seed"]}') - if 'width' in params: + if "width" in params: switches.append(f'-W {params["width"]}') - if 'height' in params: + if "height" in params: switches.append(f'-H {params["height"]}') - if 'cfg_scale' in params: + if "cfg_scale" in params: switches.append(f'-C {params["cfg_scale"]}') - if 'sampler_name' in params: + if "sampler_name" in params: switches.append(f'-A {params["sampler_name"]}') - if 'seamless' in params and params["seamless"] == True: - switches.append(f'--seamless') - if 'init_img' in params and len(params['init_img']) > 0: + if "seamless" in params and params["seamless"] == True: + switches.append(f"--seamless") + if "init_img" in params and len(params["init_img"]) > 0: switches.append(f'-I {params["init_img"]}') - if 'init_mask' in params and len(params['init_mask']) > 0: + if "init_mask" in params and len(params["init_mask"]) > 0: switches.append(f'-M {params["init_mask"]}') - if 'init_color' in params and len(params['init_color']) > 0: + if "init_color" in params and len(params["init_color"]) > 0: switches.append(f'--init_color {params["init_color"]}') - if 'strength' in params and 'init_img' in params: + if "strength" in params and "init_img" in params: switches.append(f'-f {params["strength"]}') - if 'fit' in params and params["fit"] == True: - switches.append(f'--fit') - if 'gfpgan_strength' in params and params["gfpgan_strength"]: + if "fit" in params and params["fit"] == True: + switches.append(f"--fit") + if "gfpgan_strength" in params and params["gfpgan_strength"]: switches.append(f'-G {params["gfpgan_strength"]}') - if 'upscale' in params and params["upscale"]: + if "upscale" in params and params["upscale"]: switches.append(f'-U {params["upscale"][0]} {params["upscale"][1]}') - if 'variation_amount' in params and params['variation_amount'] > 0: + if "variation_amount" in params and params["variation_amount"] > 0: switches.append(f'-v {params["variation_amount"]}') - if 'with_variations' in params: - seed_weight_pairs = ','.join(f'{seed}:{weight}' for seed, weight in params["with_variations"]) - switches.append(f'-V {seed_weight_pairs}') + if "with_variations" in params: + seed_weight_pairs = ",".join( + f"{seed}:{weight}" for seed, weight in params["with_variations"] + ) + switches.append(f"-V {seed_weight_pairs}") - return ' '.join(switches) - - - -def create_cmd_parser(): - """ - This is simply a copy of the parser from `dream.py` with a change to give - prompt a default value. This is a temporary hack pending merge of #587 which - provides a better way to do this. - """ - parser = argparse.ArgumentParser( - description='Example: dream> a fantastic alien landscape -W1024 -H960 -s100 -n12', - exit_on_error=True, - ) - parser.add_argument('prompt', nargs='?', default='') - parser.add_argument('-s', '--steps', type=int, help='Number of steps') - parser.add_argument( - '-S', - '--seed', - type=int, - help='Image seed; a +ve integer, or use -1 for the previous seed, -2 for the one before that, etc', - ) - parser.add_argument( - '-n', - '--iterations', - type=int, - default=1, - help='Number of samplings to perform (slower, but will provide seeds for individual images)', - ) - parser.add_argument( - '-W', '--width', type=int, help='Image width, multiple of 64' - ) - parser.add_argument( - '-H', '--height', type=int, help='Image height, multiple of 64' - ) - parser.add_argument( - '-C', - '--cfg_scale', - default=7.5, - type=float, - help='Classifier free guidance (CFG) scale - higher numbers cause generator to "try" harder.', - ) - parser.add_argument( - '-g', '--grid', action='store_true', help='generate a grid' - ) - parser.add_argument( - '--outdir', - '-o', - type=str, - default=None, - help='Directory to save generated images and a log of prompts and seeds', - ) - parser.add_argument( - '--seamless', - action='store_true', - help='Change the model to seamless tiling (circular) mode', - ) - parser.add_argument( - '-i', - '--individual', - action='store_true', - help='Generate individual files (default)', - ) - parser.add_argument( - '-I', - '--init_img', - type=str, - help='Path to input image for img2img mode (supersedes width and height)', - ) - parser.add_argument( - '-M', - '--init_mask', - type=str, - help='Path to input mask for inpainting mode (supersedes width and height)', - ) - parser.add_argument( - '--init_color', - type=str, - help='Path to reference image for color correction (used for repeated img2img and inpainting)' - ) - parser.add_argument( - '-T', - '-fit', - '--fit', - action='store_true', - help='If specified, will resize the input image to fit within the dimensions of width x height (512x512 default)', - ) - parser.add_argument( - '-f', - '--strength', - default=0.75, - type=float, - help='Strength for noising/unnoising. 0.0 preserves image exactly, 1.0 replaces it completely', - ) - parser.add_argument( - '-G', - '--gfpgan_strength', - default=0, - type=float, - help='The strength at which to apply the GFPGAN model to the result, in order to improve faces.', - ) - parser.add_argument( - '-U', - '--upscale', - nargs='+', - default=None, - type=float, - help='Scale factor (2, 4) for upscaling followed by upscaling strength (0-1.0). If strength not specified, defaults to 0.75' - ) - parser.add_argument( - '-save_orig', - '--save_original', - action='store_true', - help='Save original. Use it when upscaling to save both versions.', - ) - # variants is going to be superseded by a generalized "prompt-morph" function - # parser.add_argument('-v','--variants',type=int,help="in img2img mode, the first generated image will get passed back to img2img to generate the requested number of variants") - parser.add_argument( - '-x', - '--skip_normalize', - action='store_true', - help='Skip subprompt weight normalization', - ) - parser.add_argument( - '-A', - '-m', - '--sampler', - dest='sampler_name', - default=None, - type=str, - choices=SAMPLER_CHOICES, - metavar='SAMPLER_NAME', - help=f'Switch to a different sampler. Supported samplers: {", ".join(SAMPLER_CHOICES)}', - ) - parser.add_argument( - '-t', - '--log_tokenization', - action='store_true', - help='shows how the prompt is split into tokens' - ) - parser.add_argument( - '-v', - '--variation_amount', - default=0.0, - type=float, - help='If > 0, generates variations on the initial seed instead of random seeds per iteration. Must be between 0 and 1. Higher values will be more different.' - ) - parser.add_argument( - '-V', - '--with_variations', - default=None, - type=str, - help='list of variations to apply, in the format `seed:weight,seed:weight,...' - ) - return parser + return " ".join(switches) diff --git a/backend/server.py b/backend/server.py index ef93c5b0d7..11d6c61502 100644 --- a/backend/server.py +++ b/backend/server.py @@ -6,7 +6,8 @@ import traceback import eventlet import glob import shlex -import argparse +import math +import shutil from flask_socketio import SocketIO from flask import Flask, send_from_directory, url_for, jsonify @@ -15,13 +16,16 @@ from PIL import Image from pytorch_lightning import logging from threading import Event from uuid import uuid4 +from send2trash import send2trash from ldm.gfpgan.gfpgan_tools import real_esrgan_upscale from ldm.gfpgan.gfpgan_tools import run_gfpgan from ldm.generate import Generate from ldm.dream.pngwriter import PngWriter, retrieve_metadata +from ldm.dream.args import APP_ID, APP_VERSION, calculate_init_img_hash +from ldm.dream.conditioning import split_weighted_subprompts -from modules.parameters import parameters_to_command, create_cmd_parser +from modules.parameters import parameters_to_command """ @@ -29,12 +33,14 @@ USER CONFIG """ output_dir = "outputs/" # Base output directory for images -#host = 'localhost' # Web & socket.io host -host = '0.0.0.0' # Web & socket.io host +# host = 'localhost' # Web & socket.io host +host = "localhost" # Web & socket.io host port = 9090 # Web & socket.io port -verbose = False # enables copious socket.io logging -additional_allowed_origins = ['http://localhost:9090'] # additional CORS allowed origins - +verbose = False # enables copious socket.io logging +additional_allowed_origins = [ + "http://localhost:5173" +] # additional CORS allowed origins +model = "stable-diffusion-1.4" """ END USER CONFIG @@ -47,26 +53,23 @@ SERVER SETUP # fix missing mimetypes on windows due to registry wonkiness -mimetypes.add_type('application/javascript', '.js') -mimetypes.add_type('text/css', '.css') +mimetypes.add_type("application/javascript", ".js") +mimetypes.add_type("text/css", ".css") -app = Flask(__name__, static_url_path='', static_folder='../frontend/dist/') +app = Flask(__name__, static_url_path="", static_folder="../frontend/dist/") -app.config['OUTPUTS_FOLDER'] = "../outputs" +app.config["OUTPUTS_FOLDER"] = "../outputs" -@app.route('/outputs/') +@app.route("/outputs/") def outputs(filename): - return send_from_directory( - app.config['OUTPUTS_FOLDER'], - filename - ) + return send_from_directory(app.config["OUTPUTS_FOLDER"], filename) -@app.route("/", defaults={'path': ''}) +@app.route("/", defaults={"path": ""}) def serve(path): - return send_from_directory(app.static_folder, 'index.html') + return send_from_directory(app.static_folder, "index.html") logger = True if verbose else False @@ -78,12 +81,12 @@ max_http_buffer_size = 10000000 cors_allowed_origins = [f"http://{host}:{port}"] + additional_allowed_origins socketio = SocketIO( - app, - logger=logger, - engineio_logger=engineio_logger, - max_http_buffer_size=max_http_buffer_size, - cors_allowed_origins=cors_allowed_origins, - ) + app, + logger=logger, + engineio_logger=engineio_logger, + max_http_buffer_size=max_http_buffer_size, + cors_allowed_origins=cors_allowed_origins, +) """ @@ -104,29 +107,31 @@ canceled = Event() # reduce logging outputs to error transformers.logging.set_verbosity_error() -logging.getLogger('pytorch_lightning').setLevel(logging.ERROR) +logging.getLogger("pytorch_lightning").setLevel(logging.ERROR) # Initialize and load model -model = Generate() -model.load_model() +generate = Generate(model) +generate.load_model() # location for "finished" images -result_path = os.path.join(output_dir, 'img-samples/') +result_path = os.path.join(output_dir, "img-samples/") # temporary path for intermediates -intermediate_path = os.path.join(result_path, 'intermediates/') +intermediate_path = os.path.join(result_path, "intermediates/") # path for user-uploaded init images and masks -init_path = os.path.join(result_path, 'init-images/') -mask_path = os.path.join(result_path, 'mask-images/') +init_image_path = os.path.join(result_path, "init-images/") +mask_image_path = os.path.join(result_path, "mask-images/") # txt log -log_path = os.path.join(result_path, 'dream_log.txt') +log_path = os.path.join(result_path, "dream_log.txt") # make all output paths -[os.makedirs(path, exist_ok=True) - for path in [result_path, intermediate_path, init_path, mask_path]] +[ + os.makedirs(path, exist_ok=True) + for path in [result_path, intermediate_path, init_image_path, mask_image_path] +] """ @@ -139,126 +144,219 @@ SOCKET.IO LISTENERS """ -@socketio.on('requestAllImages') +@socketio.on("requestSystemConfig") +def handle_request_capabilities(): + print(f">> System config requested") + config = get_system_config() + socketio.emit("systemConfig", config) + + +@socketio.on("requestAllImages") def handle_request_all_images(): - print(f'>> All images requested') - parser = create_cmd_parser() + print(f">> All images requested") paths = list(filter(os.path.isfile, glob.glob(result_path + "*.png"))) paths.sort(key=lambda x: os.path.getmtime(x)) image_array = [] for path in paths: - # image = Image.open(path) - all_metadata = retrieve_metadata(path) - if 'Dream' in all_metadata and not all_metadata['sd-metadata']: - metadata = vars(parser.parse_args(shlex.split(all_metadata['Dream']))) - else: - metadata = all_metadata['sd-metadata'] - image_array.append({'path': path, 'metadata': metadata}) - return make_response("OK", data=image_array) + metadata = retrieve_metadata(path) + image_array.append({"url": path, "metadata": metadata["sd-metadata"]}) + socketio.emit("galleryImages", {"images": image_array}) + eventlet.sleep(0) -@socketio.on('generateImage') -def handle_generate_image_event(generation_parameters, esrgan_parameters, gfpgan_parameters): - print(f'>> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}') - generate_images( - generation_parameters, - esrgan_parameters, - gfpgan_parameters +@socketio.on("generateImage") +def handle_generate_image_event( + generation_parameters, esrgan_parameters, gfpgan_parameters +): + print( + f">> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}" ) - return make_response("OK") + generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters) -@socketio.on('runESRGAN') +@socketio.on("runESRGAN") def handle_run_esrgan_event(original_image, esrgan_parameters): - print(f'>> ESRGAN upscale requested for "{original_image["url"]}": {esrgan_parameters}') + print( + f'>> ESRGAN upscale requested for "{original_image["url"]}": {esrgan_parameters}' + ) + progress = { + "currentStep": 1, + "totalSteps": 1, + "currentIteration": 1, + "totalIterations": 1, + "currentStatus": "Preparing", + "isProcessing": True, + "currentStatusHasSteps": False, + } + + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + image = Image.open(original_image["url"]) - seed = original_image['metadata']['seed'] if 'seed' in original_image['metadata'] else 'unknown_seed' + seed = ( + original_image["metadata"]["seed"] + if "seed" in original_image["metadata"] + else "unknown_seed" + ) + + progress["currentStatus"] = "Upscaling" + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) image = real_esrgan_upscale( image=image, - upsampler_scale=esrgan_parameters['upscale'][0], - strength=esrgan_parameters['upscale'][1], - seed=seed + upsampler_scale=esrgan_parameters["upscale"][0], + strength=esrgan_parameters["upscale"][1], + seed=seed, ) - esrgan_parameters['seed'] = seed - path = save_image(image, esrgan_parameters, result_path, postprocessing='esrgan') + progress["currentStatus"] = "Saving image" + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + + esrgan_parameters["seed"] = seed + metadata = parameters_to_post_processed_image_metadata( + parameters=esrgan_parameters, + original_image_path=original_image["url"], + type="esrgan", + ) command = parameters_to_command(esrgan_parameters) + path = save_image(image, command, metadata, result_path, postprocessing="esrgan") + write_log_message(f'[Upscaled] "{original_image["url"]}" > "{path}": {command}') + progress["currentStatus"] = "Finished" + progress["currentStep"] = 0 + progress["totalSteps"] = 0 + progress["currentIteration"] = 0 + progress["totalIterations"] = 0 + progress["isProcessing"] = False + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + socketio.emit( - 'result', {'url': os.path.relpath(path), 'type': 'esrgan', 'uuid': original_image['uuid'],'metadata': esrgan_parameters}) + "esrganResult", + { + "url": os.path.relpath(path), + "metadata": metadata, + }, + ) - -@socketio.on('runGFPGAN') +@socketio.on("runGFPGAN") def handle_run_gfpgan_event(original_image, gfpgan_parameters): - print(f'>> GFPGAN face fix requested for "{original_image["url"]}": {gfpgan_parameters}') + print( + f'>> GFPGAN face fix requested for "{original_image["url"]}": {gfpgan_parameters}' + ) + progress = { + "currentStep": 1, + "totalSteps": 1, + "currentIteration": 1, + "totalIterations": 1, + "currentStatus": "Preparing", + "isProcessing": True, + "currentStatusHasSteps": False, + } + + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + image = Image.open(original_image["url"]) - seed = original_image['metadata']['seed'] if 'seed' in original_image['metadata'] else 'unknown_seed' + seed = ( + original_image["metadata"]["seed"] + if "seed" in original_image["metadata"] + else "unknown_seed" + ) + + progress["currentStatus"] = "Fixing faces" + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) image = run_gfpgan( image=image, - strength=gfpgan_parameters['gfpgan_strength'], + strength=gfpgan_parameters["gfpgan_strength"], seed=seed, - upsampler_scale=1 + upsampler_scale=1, ) - gfpgan_parameters['seed'] = seed - path = save_image(image, gfpgan_parameters, result_path, postprocessing='gfpgan') + progress["currentStatus"] = "Saving image" + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + + gfpgan_parameters["seed"] = seed + metadata = parameters_to_post_processed_image_metadata( + parameters=gfpgan_parameters, + original_image_path=original_image["url"], + type="gfpgan", + ) command = parameters_to_command(gfpgan_parameters) + path = save_image(image, command, metadata, result_path, postprocessing="gfpgan") + write_log_message(f'[Fixed faces] "{original_image["url"]}" > "{path}": {command}') + progress["currentStatus"] = "Finished" + progress["currentStep"] = 0 + progress["totalSteps"] = 0 + progress["currentIteration"] = 0 + progress["totalIterations"] = 0 + progress["isProcessing"] = False + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + socketio.emit( - 'result', {'url': os.path.relpath(path), 'type': 'gfpgan', 'uuid': original_image['uuid'],'metadata': gfpgan_parameters}) + "gfpganResult", + { + "url": os.path.relpath(path), + "metadata": metadata, + }, + ) -@socketio.on('cancel') +@socketio.on("cancel") def handle_cancel(): - print(f'>> Cancel processing requested') + print(f">> Cancel processing requested") canceled.set() - return make_response("OK") + socketio.emit("processingCanceled") # TODO: I think this needs a safety mechanism. -@socketio.on('deleteImage') -def handle_delete_image(path): +@socketio.on("deleteImage") +def handle_delete_image(path, uuid): print(f'>> Delete requested "{path}"') - Path(path).unlink() - return make_response("OK") + send2trash(path) + socketio.emit("imageDeleted", {"url": path, "uuid": uuid}) # TODO: I think this needs a safety mechanism. -@socketio.on('uploadInitialImage') +@socketio.on("uploadInitialImage") def handle_upload_initial_image(bytes, name): print(f'>> Init image upload requested "{name}"') uuid = uuid4().hex split = os.path.splitext(name) - name = f'{split[0]}.{uuid}{split[1]}' - file_path = os.path.join(init_path, name) + name = f"{split[0]}.{uuid}{split[1]}" + file_path = os.path.join(init_image_path, name) os.makedirs(os.path.dirname(file_path), exist_ok=True) newFile = open(file_path, "wb") newFile.write(bytes) - return make_response("OK", data=file_path) + socketio.emit("initialImageUploaded", {"url": file_path, "uuid": ""}) # TODO: I think this needs a safety mechanism. -@socketio.on('uploadMaskImage') +@socketio.on("uploadMaskImage") def handle_upload_mask_image(bytes, name): print(f'>> Mask image upload requested "{name}"') uuid = uuid4().hex split = os.path.splitext(name) - name = f'{split[0]}.{uuid}{split[1]}' - file_path = os.path.join(mask_path, name) + name = f"{split[0]}.{uuid}{split[1]}" + file_path = os.path.join(mask_image_path, name) os.makedirs(os.path.dirname(file_path), exist_ok=True) newFile = open(file_path, "wb") newFile.write(bytes) - return make_response("OK", data=file_path) - + socketio.emit("maskImageUploaded", {"url": file_path, "uuid": ""}) """ @@ -266,114 +364,343 @@ END SOCKET.IO LISTENERS """ - """ ADDITIONAL FUNCTIONS """ +def get_system_config(): + return { + "model": "stable diffusion", + "model_id": model, + "model_hash": generate.model_hash, + "app_id": APP_ID, + "app_version": APP_VERSION, + } + + +def parameters_to_post_processed_image_metadata(parameters, original_image_path, type): + # top-level metadata minus `image` or `images` + metadata = get_system_config() + + orig_hash = calculate_init_img_hash(original_image_path) + + image = {"orig_path": original_image_path, "orig_hash": orig_hash} + + if type == "esrgan": + image["type"] = "esrgan" + image["scale"] = parameters["upscale"][0] + image["strength"] = parameters["upscale"][1] + elif type == "gfpgan": + image["type"] = "gfpgan" + image["strength"] = parameters["gfpgan_strength"] + else: + raise TypeError(f"Invalid type: {type}") + + metadata["image"] = image + return metadata + + +def parameters_to_generated_image_metadata(parameters): + # top-level metadata minus `image` or `images` + + metadata = get_system_config() + # remove any image keys not mentioned in RFC #266 + rfc266_img_fields = [ + "type", + "postprocessing", + "sampler", + "prompt", + "seed", + "variations", + "steps", + "cfg_scale", + "step_number", + "width", + "height", + "extra", + "seamless", + ] + + rfc_dict = {} + + for item in parameters.items(): + key, value = item + if key in rfc266_img_fields: + rfc_dict[key] = value + + postprocessing = [] + + # 'postprocessing' is either null or an + if "gfpgan_strength" in parameters: + + postprocessing.append( + {"type": "gfpgan", "strength": float(parameters["gfpgan_strength"])} + ) + + if "upscale" in parameters: + postprocessing.append( + { + "type": "esrgan", + "scale": int(parameters["upscale"][0]), + "strength": float(parameters["upscale"][1]), + } + ) + + rfc_dict["postprocessing"] = postprocessing if len(postprocessing) > 0 else None + + # semantic drift + rfc_dict["sampler"] = parameters["sampler_name"] + + # display weighted subprompts (liable to change) + subprompts = split_weighted_subprompts(parameters["prompt"]) + subprompts = [{"prompt": x[0], "weight": x[1]} for x in subprompts] + rfc_dict["prompt"] = subprompts + + # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs + variations = [] + + if "with_variations" in parameters: + variations = [ + {"seed": x[0], "weight": x[1]} for x in parameters["with_variations"] + ] + + rfc_dict["variations"] = variations + + if "init_img" in parameters: + rfc_dict["type"] = "img2img" + rfc_dict["strength"] = parameters["strength"] + rfc_dict["fit"] = parameters["fit"] # TODO: Noncompliant + rfc_dict["orig_hash"] = calculate_init_img_hash(parameters["init_img"]) + rfc_dict["init_image_path"] = parameters["init_img"] # TODO: Noncompliant + rfc_dict["sampler"] = "ddim" # TODO: FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS + if "init_mask" in parameters: + rfc_dict["mask_hash"] = calculate_init_img_hash( + parameters["init_mask"] + ) # TODO: Noncompliant + rfc_dict["mask_image_path"] = parameters["init_mask"] # TODO: Noncompliant + else: + rfc_dict["type"] = "txt2img" + + metadata["image"] = rfc_dict + + return metadata + + +def make_unique_init_image_filename(name): + uuid = uuid4().hex + split = os.path.splitext(name) + name = f"{split[0]}.{uuid}{split[1]}" + return name + + def write_log_message(message, log_path=log_path): """Logs the filename and parameters used to generate or process that image to log file""" - message = f'{message}\n' - with open(log_path, 'a', encoding='utf-8') as file: + message = f"{message}\n" + with open(log_path, "a", encoding="utf-8") as file: file.writelines(message) -def make_response(status, message=None, data=None): - response = {'status': status} - if message is not None: - response['message'] = message - if data is not None: - response['data'] = data - return response - - -def save_image(image, parameters, output_dir, step_index=None, postprocessing=False): - seed = parameters['seed'] if 'seed' in parameters else 'unknown_seed' - +def save_image( + image, command, metadata, output_dir, step_index=None, postprocessing=False +): pngwriter = PngWriter(output_dir) prefix = pngwriter.unique_prefix() - filename = f'{prefix}.{seed}' + seed = "unknown_seed" + + if "image" in metadata: + if "seed" in metadata["image"]: + seed = metadata["image"]["seed"] + + filename = f"{prefix}.{seed}" if step_index: - filename += f'.{step_index}' + filename += f".{step_index}" if postprocessing: - filename += f'.postprocessed' + filename += f".postprocessed" - filename += '.png' + filename += ".png" - command = parameters_to_command(parameters) - - path = pngwriter.save_image_and_prompt_to_png(image, command, metadata=parameters, name=filename) + path = pngwriter.save_image_and_prompt_to_png( + image=image, dream_prompt=command, metadata=metadata, name=filename + ) return path + +def calculate_real_steps(steps, strength, has_init_image): + return math.floor(strength * steps) if has_init_image else steps + + def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters): canceled.clear() step_index = 1 + """ + If a result image is used as an init image, and then deleted, we will want to be + able to use it as an init image in the future. Need to copy it. + + If the init/mask image doesn't exist in the init_image_path/mask_image_path, + make a unique filename for it and copy it there. + """ + if "init_img" in generation_parameters: + filename = os.path.basename(generation_parameters["init_img"]) + if not os.path.exists(os.path.join(init_image_path, filename)): + unique_filename = make_unique_init_image_filename(filename) + new_path = os.path.join(init_image_path, unique_filename) + shutil.copy(generation_parameters["init_img"], new_path) + generation_parameters["init_img"] = new_path + if "init_mask" in generation_parameters: + filename = os.path.basename(generation_parameters["init_mask"]) + if not os.path.exists(os.path.join(mask_image_path, filename)): + unique_filename = make_unique_init_image_filename(filename) + new_path = os.path.join(init_image_path, unique_filename) + shutil.copy(generation_parameters["init_img"], new_path) + generation_parameters["init_mask"] = new_path + + totalSteps = calculate_real_steps( + steps=generation_parameters["steps"], + strength=generation_parameters["strength"] + if "strength" in generation_parameters + else None, + has_init_image="init_img" in generation_parameters, + ) + + progress = { + "currentStep": 1, + "totalSteps": totalSteps, + "currentIteration": 1, + "totalIterations": generation_parameters["iterations"], + "currentStatus": "Preparing", + "isProcessing": True, + "currentStatusHasSteps": False, + } + + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + def image_progress(sample, step): if canceled.is_set(): raise CanceledException + nonlocal step_index nonlocal generation_parameters - if generation_parameters["progress_images"] and step % 5 == 0 and step < generation_parameters['steps'] - 1: - image = model.sample_to_image(sample) - path = save_image(image, generation_parameters, intermediate_path, step_index) + nonlocal progress + + progress["currentStep"] = step + 1 + progress["currentStatus"] = "Generating" + progress["currentStatusHasSteps"] = True + + if ( + generation_parameters["progress_images"] + and step % 5 == 0 + and step < generation_parameters["steps"] - 1 + ): + image = generate.sample_to_image(sample) + path = save_image( + image, generation_parameters, intermediate_path, step_index + ) step_index += 1 - socketio.emit('intermediateResult', { - 'url': os.path.relpath(path), 'metadata': generation_parameters}) - socketio.emit('progress', {'step': step + 1}) + socketio.emit( + "intermediateResult", + {"url": os.path.relpath(path), "metadata": generation_parameters}, + ) + socketio.emit("progressUpdate", progress) eventlet.sleep(0) def image_done(image, seed): nonlocal generation_parameters nonlocal esrgan_parameters nonlocal gfpgan_parameters + nonlocal progress + + step_index = 1 + + progress["currentStatus"] = "Generation complete" + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) all_parameters = generation_parameters postprocessing = False if esrgan_parameters: + progress["currentStatus"] = "Upscaling" + progress["currentStatusHasSteps"] = False + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + image = real_esrgan_upscale( image=image, - strength=esrgan_parameters['strength'], - upsampler_scale=esrgan_parameters['level'], - seed=seed + strength=esrgan_parameters["strength"], + upsampler_scale=esrgan_parameters["level"], + seed=seed, ) postprocessing = True - all_parameters["upscale"] = [esrgan_parameters['level'], esrgan_parameters['strength']] + all_parameters["upscale"] = [ + esrgan_parameters["level"], + esrgan_parameters["strength"], + ] if gfpgan_parameters: + progress["currentStatus"] = "Fixing faces" + progress["currentStatusHasSteps"] = False + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + image = run_gfpgan( image=image, - strength=gfpgan_parameters['strength'], + strength=gfpgan_parameters["strength"], seed=seed, upsampler_scale=1, ) postprocessing = True - all_parameters["gfpgan_strength"] = gfpgan_parameters['strength'] + all_parameters["gfpgan_strength"] = gfpgan_parameters["strength"] - all_parameters['seed'] = seed + all_parameters["seed"] = seed + progress["currentStatus"] = "Saving image" + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) - path = save_image(image, all_parameters, result_path, postprocessing=postprocessing) + metadata = parameters_to_generated_image_metadata(all_parameters) command = parameters_to_command(all_parameters) - print(f'Image generated: "{path}"') + path = save_image( + image, command, metadata, result_path, postprocessing=postprocessing + ) + + print(f'>> Image generated: "{path}"') write_log_message(f'[Generated] "{path}": {command}') + if progress["totalIterations"] > progress["currentIteration"]: + progress["currentStep"] = 1 + progress["currentIteration"] += 1 + progress["currentStatus"] = "Iteration finished" + progress["currentStatusHasSteps"] = False + else: + progress["currentStep"] = 0 + progress["totalSteps"] = 0 + progress["currentIteration"] = 0 + progress["totalIterations"] = 0 + progress["currentStatus"] = "Finished" + progress["isProcessing"] = False + + socketio.emit("progressUpdate", progress) + eventlet.sleep(0) + socketio.emit( - 'result', {'url': os.path.relpath(path), 'type': 'generation', 'metadata': all_parameters}) + "generationResult", + {"url": os.path.relpath(path), "metadata": metadata}, + ) eventlet.sleep(0) try: - model.prompt2image( + generate.prompt2image( **generation_parameters, step_callback=image_progress, - image_callback=image_done + image_callback=image_done, ) except KeyboardInterrupt: @@ -381,7 +708,7 @@ def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters) except CanceledException: pass except Exception as e: - socketio.emit('error', (str(e))) + socketio.emit("error", {"message": (str(e))}) print("\n") traceback.print_exc() print("\n") @@ -392,6 +719,6 @@ END ADDITIONAL FUNCTIONS """ -if __name__ == '__main__': - print(f'Starting server at http://{host}:{port}') +if __name__ == "__main__": + print(f">> Starting server at http://{host}:{port}") socketio.run(app, host=host, port=port) diff --git a/docker-build/Dockerfile b/docker-build/Dockerfile new file mode 100644 index 0000000000..dd6d898ce5 --- /dev/null +++ b/docker-build/Dockerfile @@ -0,0 +1,57 @@ +FROM debian + +ARG gsd +ENV GITHUB_STABLE_DIFFUSION $gsd + +ARG rsd +ENV REQS $rsd + +ARG cs +ENV CONDA_SUBDIR $cs + +ENV PIP_EXISTS_ACTION="w" + +# TODO: Optimize image size + +SHELL ["/bin/bash", "-c"] + +WORKDIR / +RUN apt update && apt upgrade -y \ + && apt install -y \ + git \ + libgl1-mesa-glx \ + libglib2.0-0 \ + pip \ + python3 \ + && git clone $GITHUB_STABLE_DIFFUSION + +# Install Anaconda or Miniconda +COPY anaconda.sh . +RUN bash anaconda.sh -b -u -p /anaconda && /anaconda/bin/conda init bash + +# SD +WORKDIR /stable-diffusion +RUN source ~/.bashrc \ + && conda create -y --name ldm && conda activate ldm \ + && conda config --env --set subdir $CONDA_SUBDIR \ + && pip3 install -r $REQS \ + && pip3 install basicsr facexlib realesrgan \ + && mkdir models/ldm/stable-diffusion-v1 \ + && ln -s "/data/sd-v1-4.ckpt" models/ldm/stable-diffusion-v1/model.ckpt + +# Face restoreation +# by default expected in a sibling directory to stable-diffusion +WORKDIR / +RUN git clone https://github.com/TencentARC/GFPGAN.git + +WORKDIR /GFPGAN +RUN pip3 install -r requirements.txt \ + && python3 setup.py develop \ + && ln -s "/data/GFPGANv1.3.pth" experiments/pretrained_models/GFPGANv1.3.pth + +WORKDIR /stable-diffusion +RUN python3 scripts/preload_models.py + +WORKDIR / +COPY entrypoint.sh . +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/docker-build/entrypoint.sh b/docker-build/entrypoint.sh new file mode 100755 index 0000000000..f47e6669e0 --- /dev/null +++ b/docker-build/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd /stable-diffusion + +if [ $# -eq 0 ]; then + python3 scripts/dream.py --full_precision -o /data + # bash +else + python3 scripts/dream.py --full_precision -o /data "$@" +fi \ No newline at end of file diff --git a/docs/assets/logo.png b/docs/assets/logo.png index fa0548ff78..b6eb33a6db 100644 Binary files a/docs/assets/logo.png and b/docs/assets/logo.png differ diff --git a/assets/stable-samples/img2img/mountains-2.png b/docs/assets/stable-samples/img2img/mountains-2.png similarity index 100% rename from assets/stable-samples/img2img/mountains-2.png rename to docs/assets/stable-samples/img2img/mountains-2.png diff --git a/assets/stable-samples/img2img/mountains-3.png b/docs/assets/stable-samples/img2img/mountains-3.png similarity index 100% rename from assets/stable-samples/img2img/mountains-3.png rename to docs/assets/stable-samples/img2img/mountains-3.png diff --git a/assets/stable-samples/img2img/sketch-mountains-input.jpg b/docs/assets/stable-samples/img2img/sketch-mountains-input.jpg similarity index 100% rename from assets/stable-samples/img2img/sketch-mountains-input.jpg rename to docs/assets/stable-samples/img2img/sketch-mountains-input.jpg diff --git a/assets/stable-samples/txt2img/merged-0005.png b/docs/assets/stable-samples/txt2img/merged-0005.png similarity index 100% rename from assets/stable-samples/txt2img/merged-0005.png rename to docs/assets/stable-samples/txt2img/merged-0005.png diff --git a/assets/stable-samples/txt2img/merged-0006.png b/docs/assets/stable-samples/txt2img/merged-0006.png similarity index 100% rename from assets/stable-samples/txt2img/merged-0006.png rename to docs/assets/stable-samples/txt2img/merged-0006.png diff --git a/assets/stable-samples/txt2img/merged-0007.png b/docs/assets/stable-samples/txt2img/merged-0007.png similarity index 100% rename from assets/stable-samples/txt2img/merged-0007.png rename to docs/assets/stable-samples/txt2img/merged-0007.png diff --git a/assets/v1-variants-scores.jpg b/docs/assets/v1-variants-scores.jpg similarity index 100% rename from assets/v1-variants-scores.jpg rename to docs/assets/v1-variants-scores.jpg diff --git a/docs/features/CHANGELOG.md b/docs/features/CHANGELOG.md index f50c4f11b8..a6258f6a56 100644 --- a/docs/features/CHANGELOG.md +++ b/docs/features/CHANGELOG.md @@ -2,6 +2,8 @@ title: Changelog --- +# :octicons-log-16: Changelog + ## v1.13 (in process) - Supports a Google Colab notebook for a standalone server running on Google diff --git a/docs/features/CLI.md b/docs/features/CLI.md index 5f7cdaf162..aab695bbcd 100644 --- a/docs/features/CLI.md +++ b/docs/features/CLI.md @@ -1,27 +1,34 @@ --- title: CLI +hide: + - toc --- +# :material-bash: CLI + ## **Interactive Command Line Interface** The `dream.py` script, located in `scripts/dream.py`, provides an interactive interface to image generation similar to the "dream mothership" bot that Stable AI provided on its Discord server. -Unlike the txt2img.py and img2img.py scripts provided in the original CompViz/stable-diffusion -source code repository, the time-consuming initialization of the AI model initialization only -happens once. After that image generation from the command-line interface is very fast. +Unlike the `txt2img.py` and `img2img.py` scripts provided in the original +[CompVis/stable-diffusion](https://github.com/CompVis/stable-diffusion) source code repository, the +time-consuming initialization of the AI model initialization only happens once. After that image +generation from the command-line interface is very fast. -The script uses the readline library to allow for in-line editing, command history (up and down -arrows), autocompletion, and more. To help keep track of which prompts generated which images, the +The script uses the readline library to allow for in-line editing, command history (++up++ and +++down++), autocompletion, and more. To help keep track of which prompts generated which images, the script writes a log file of image names and prompts to the selected output directory. In addition, as of version 1.02, it also writes the prompt into the PNG file's metadata where it can -be retrieved using scripts/images2prompt.py +be retrieved using `scripts/images2prompt.py` The script is confirmed to work on Linux, Windows and Mac systems. -_Note:_ This script runs from the command-line or can be used as a Web application. The Web GUI is -currently rudimentary, but a much better replacement is on its way. +!!! note + + This script runs from the command-line or can be used as a Web application. The Web GUI is + currently rudimentary, but a much better replacement is on its way. ```bash (ldm) ~/stable-diffusion$ python3 ./scripts/dream.py @@ -47,185 +54,197 @@ dream> q 00011.png: "there's a fly in my soup" -n6 -g -S 2685670268 ``` -

- -

+![dream-py-demo](../assets/dream-py-demo.png) The `dream>` prompt's arguments are pretty much identical to those used in the Discord bot, except you don't need to type "!dream" (it doesn't hurt if you do). A significant change is that creation -of individual images is now the default unless --grid (-g) is given. A full list is given in [List -of prompt arguments] (#list-of-prompt-arguments). +of individual images is now the default unless `--grid` (`-g`) is given. A full list is given in +[List of prompt arguments](#list-of-prompt-arguments). ## Arguments The script itself also recognizes a series of command-line switches that will change important global defaults, such as the directory for image outputs and the location of the model weight files. -## List of arguments recognized at the command line +### List of arguments recognized at the command line -These command-line arguments can be passed to dream.py when you first run it from the Windows, Mac +These command-line arguments can be passed to `dream.py` when you first run it from the Windows, Mac or Linux command line. Some set defaults that can be overridden on a per-prompt basis (see [List of prompt arguments] (#list-of-prompt-arguments). Others -| Argument | Shortcut | Default | Description | -| :---------------------- | :---------: | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | -| --help | -h | | Print a concise help message. | -| --outdir | -o | outputs/img_samples | Location for generated images. | -| --prompt_as_dir | -p | False | Name output directories using the prompt text. | -| --from_file | | None | Read list of prompts from a file. Use "-" to read from standard input | -| --model | | stable-diffusion-1.4 | Loads model specified in configs/models.yaml. Currently one of "stable-diffusion-1.4" or "laion400m" | -| --full_precision | -F | False | Run in slower full-precision mode. Needed for Macintosh M1/M2 hardware and some older video cards. | -| --web | | False | Start in web server mode | -| --host | | localhost | Which network interface web server should listen on. Set to 0.0.0.0 to listen on any. | -| --port | | 9090 | Which port web server should listen for requests on. | -| --config | | configs/models.yaml | Configuration file for models and their weights. | -| --iterations | -n | 1 | How many images to generate per prompt. | -| --grid | -g | False | Save all image series as a grid rather than individually. | -| --sampler | -A | k_lms | Sampler to use. Use -h to get list of available samplers. | -| --seamless | | False | Create interesting effects by tiling elements of the image. | -| --embedding_path | | None | Path to pre-trained embedding manager checkpoints, for custom models | -| --gfpgan_dir | | src/gfpgan | Path to where GFPGAN is installed. | -| --gfpgan_model_path | | experiments/pretrained_models
/GFPGANv1.3.pth | Path to GFPGAN model file, relative to --gfpgan_dir. | -| --device | -d | torch.cuda.current_device() | Device to run SD on, e.g. "cuda:0" | +| Argument | Shortcut | Default | Description | +| ----------------------------------------- | ----------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `--help` | `-h` | | Print a concise help message. | +| `--outdir ` | `-o` | `outputs/img_samples` | Location for generated images. | +| `--prompt_as_dir` | `-p` | `False` | Name output directories using the prompt text. | +| `--from_file ` | | `None` | Read list of prompts from a file. Use `-` to read from standard input | +| `--model ` | | `stable-diffusion-1.4` | Loads model specified in configs/models.yaml. Currently one of "stable-diffusion-1.4" or "laion400m" | +| `--full_precision` | `-F` | `False` | Run in slower full-precision mode. Needed for Macintosh M1/M2 hardware and some older video cards. | +| `--web` | | `False` | Start in web server mode | +| `--host ` | | `localhost` | Which network interface web server should listen on. Set to 0.0.0.0 to listen on any. | +| `--port ` | | `9090` | Which port web server should listen for requests on. | +| `--config ` | | `configs/models.yaml` | Configuration file for models and their weights. | +| `--iterations ` | `-n` | `1` | How many images to generate per prompt. | +| `--grid` | `-g` | `False` | Save all image series as a grid rather than individually. | +| `--sampler ` | `-A` | `k_lms` | Sampler to use. Use `-h` to get list of available samplers. | +| `--seamless` | | `False` | Create interesting effects by tiling elements of the image. | +| `--embedding_path ` | | `None` | Path to pre-trained embedding manager checkpoints, for custom models | +| `--gfpgan_dir` | | `src/gfpgan` | Path to where GFPGAN is installed. | +| `--gfpgan_model_path` | | `experiments/pretrained_models/GFPGANv1.3.pth` | Path to GFPGAN model file, relative to `--gfpgan_dir`. | +| `--device ` | `-d` | `torch.cuda.current_device()` | Device to run SD on, e.g. "cuda:0" | + +#### deprecated These arguments are deprecated but still work: -| Argument | Shortcut | Default | Description | -| ---------------- | -------- | ------- | --------------------------------------------------------------- | -| --weights | | None | Pth to weights file; use `--model stable-diffusion-1.4` instead | -| --laion400m | -l | False | Use older LAION400m weights; use `--model=laion400m` instead | +
-### **A note on path names:** +| Argument | Shortcut | Default | Description | +| ------------------ | -------- | ------- | --------------------------------------------------------------- | +| `--weights ` | | `None` | Pth to weights file; use `--model stable-diffusion-1.4` instead | +| `--laion400m` | `-l` | `False` | Use older LAION400m weights; use `--model=laion400m` instead | -On Windows systems, you may run into problems when passing the dream script standard backslashed -path names because the Python interpreter treats "\" as an escape. You can either double your -slashes (ick): `C:\\\\path\\\\to\\\\my\\\\file`, or use Linux/Mac style forward slashes (better): -`C:/path/to/my/file`. +
+ +!!! note + + On Windows systems, you may run into problems when passing the dream script standard backslashed + path names because the Python interpreter treats `\` as an escape. You can either double your + slashes (ick): `C:\\path\\to\\my\\file`, or use Linux/Mac style forward slashes (better): + `C:/path/to/my/file`. ### List of prompt arguments -After the dream.py script initializes, it will present you with a **dream>** prompt. Here you can -enter information to generate images from text (txt2img), to embellish an existing image or sketch -(img2img), or to selectively alter chosen regions of the image (inpainting). +After the `dream.py` script initializes, it will present you with a **`dream>`** prompt. Here you +can enter information to generate images from text (txt2img), to embellish an existing image or +sketch (img2img), or to selectively alter chosen regions of the image (inpainting). -### This is an example of txt2img +#### txt2img -```bash -dream> "waterfall and rainbow" -W640 -H480 -``` +!!! example -This will create the requested image with the dimensions 640 (width) and 480 (height). + ```bash + dream> "waterfall and rainbow" -W640 -H480 + ``` + + This will create the requested image with the dimensions 640 (width) and 480 (height). Those are the `dream` commands that apply to txt2img: -| Argument | Shortcut | Default | Description | -| --------------------------- | ---------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| "my prompt" | | | Text prompt to use. The quotation marks are optional. | -| --width | -W | 512 | Width of generated image | -| --height | -H | 512 | Height of generated image | -| --iterations | -n | 1 | How many images to generate from this prompt | -| --steps | -s | 50 | How many steps of refinement to apply | -| --cfg_scale | -C | 7.5 | How hard to try to match the prompt to the generated image; any number greater than 0.0 works, but the useful range is roughly 5.0 to 20.0 | -| --seed | -S | None | Set the random seed for the next series of images. This can be used to recreate an image generated previously. | -| --sampler | -A | k_lms | Sampler to use. Use -h to get list of available samplers. | -| --grid | -g | False | Turn on grid mode to return a single image combining all the images generated by this prompt | -| --individual | -i | True | Turn off grid mode (deprecated; leave off --grid instead) | -| --outdir | -o | outputs/img_samples | Temporarily change the location of these images | -| --seamless | | False | Activate seamless tiling for interesting effects | -| --log_tokenization | -t | False | Display a color-coded list of the parsed tokens derived from the prompt | -| --skip_normalization | -x | False | Weighted subprompts will not be normalized. See [Weighted Prompts](./OTHER.md#weighted-prompts) | -| --upscale | -U | -U 1 0.75 | Upscale image by magnification factor (2, 4), and set strength of upscaling (0.0-1.0). If strength not set, will default to 0.75. | -| --gfpgan_strength | -G | -G0 | Fix faces using the GFPGAN algorithm; argument indicates how hard the algorithm should try (0.0-1.0) | -| --save_original | -save_orig | False | When upscaling or fixing faces, this will cause the original image to be saved rather than replaced. | -| --variation | -v | 0.0 | Add a bit of noise (0.0=none, 1.0=high) to the image in order to generate a series of variations. Usually used in combination with -S and -n to generate a series a riffs on a starting image. See [Variations](./VARIATIONS.md). | -| --with_variations | -V | None | Combine two or more variations. See [Variations](./VARIATIONS.md) for now to use this. | +| Argument | Shortcut | Default | Description | +| ----------------------------------------- | ----------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `"my prompt"` | | | Text prompt to use. The quotation marks are optional. | +| `--width ` | `-W` | `512` | Width of generated image | +| `--height ` | `-H` | `512` | Height of generated image | +| `--iterations ` | `-n` | `1` | How many images to generate from this prompt | +| `--steps ` | `-s` | `50` | How many steps of refinement to apply | +| `--cfg_scale ` | `-C` | `7.5` | How hard to try to match the prompt to the generated image; any number greater than 0.0 works, but the useful range is roughly 5.0 to 20.0 | +| `--seed ` | `-S` | `None` | Set the random seed for the next series of images. This can be used to recreate an image generated previously. | +| `--sampler ` | `-A` | `k_lms` | Sampler to use. Use `-h` to get list of available samplers. | +| `--grid` | `-g` | `False` | Turn on grid mode to return a single image combining all the images generated by this prompt | +| `--individual` | `-i` | `True` | Turn off grid mode (deprecated; leave off `--grid` instead) | +| `--outdir ` | `-o` | `outputs/img_samples` | Temporarily change the location of these images | +| `--seamless` | | `False` | Activate seamless tiling for interesting effects | +| `--log_tokenization` | `-t` | `False` | Display a color-coded list of the parsed tokens derived from the prompt | +| `--skip_normalization` | `-x` | `False` | Weighted subprompts will not be normalized. See [Weighted Prompts](./OTHER.md#weighted-prompts) | +| `--upscale ` | `-U ` | `-U 1 0.75` | Upscale image by magnification factor (2, 4), and set strength of upscaling (0.0-1.0). If strength not set, will default to 0.75. | +| `--gfpgan_strength ` | `-G ` | `-G0` | Fix faces using the GFPGAN algorithm; argument indicates how hard the algorithm should try (0.0-1.0) | +| `--save_original` | `-save_orig` | `False` | When upscaling or fixing faces, this will cause the original image to be saved rather than replaced. | +| `--variation ` | `-v` | `0.0` | Add a bit of noise (0.0=none, 1.0=high) to the image in order to generate a series of variations. Usually used in combination with `-S` and `-n` to generate a series a riffs on a starting image. See [Variations](./VARIATIONS.md). | +| `--with_variations ` | `-V` | `None` | Combine two or more variations. See [Variations](./VARIATIONS.md) for now to use this. | -Note that the width and height of the image must be multiples of 64. You can provide different -values, but they will be rounded down to the nearest multiple of 64. +!!! note -### This is an example of img2img + The width and height of the image must be multiples of 64. You can provide different + values, but they will be rounded down to the nearest multiple of 64. -```bash -dream> waterfall and rainbow -I./vacation-photo.png -W640 -H480 --fit -``` +#### img2img -This will modify the indicated vacation photograph by making it more like the prompt. Results will -vary greatly depending on what is in the image. We also ask to --fit the image into a box no bigger -than 640x480. Otherwise the image size will be identical to the provided photo and you may run out -of memory if it is large. +!!! example -Repeated chaining of img2img on an image can result in significant color shifts -in the output, especially if run with lower strength. Color correction can be -run against a reference image to fix this issue. Use the original input image to the -chain as the the reference image for each step in the chain. + ```bash + dream> "waterfall and rainbow" -I./vacation-photo.png -W640 -H480 --fit + ``` + + This will modify the indicated vacation photograph by making it more like the prompt. Results will + vary greatly depending on what is in the image. We also ask to --fit the image into a box no bigger + than 640x480. Otherwise the image size will be identical to the provided photo and you may run out + of memory if it is large. + +Repeated chaining of img2img on an image can result in significant color shifts in the output, +especially if run with lower strength. Color correction can be run against a reference image to fix +this issue. Use the original input image to the chain as the the reference image for each step in +the chain. In addition to the command-line options recognized by txt2img, img2img accepts additional options: -| Argument | Shortcut | Default | Description | -| ------------------ | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| --init_img | -I | None | Path to the initialization image | -| --init_color | | None | Path to reference image for color correction | -| --fit | -F | False | Scale the image to fit into the specified -H and -W dimensions | -| --strength | -s | 0.75 | How hard to try to match the prompt to the initial image. Ranges from 0.0-0.99, with higher values replacing the initial image completely. | +| Argument | Shortcut | Default | Description | +| ----------------------------------------- | ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `--init_img ` | `-I` | `None` | Path to the initialization image | +| `--init_color ` | | `None` | Path to reference image for color correction | +| `--fit` | `-F` | `False` | Scale the image to fit into the specified -H and -W dimensions | +| `--strength ` | `-f` | `0.75` | How hard to try to match the prompt to the initial image. Ranges from 0.0-0.99, with higher values replacing the initial image completely. | -### This is an example of inpainting +#### Inpainting -```bash -dream> "waterfall and rainbow" -I./vacation-photo.png -M./vacation-mask.png -W640 -H480 --fit -``` +!!! example -This will do the same thing as img2img, but image alterations will only occur within transparent -areas defined by the mask file specified by -M. You may also supply just a single initial image with -the areas to overpaint made transparent, but you must be careful not to destroy the pixels -underneath when you create the transparent areas. See [Inpainting](./INPAINTING.md) for details. + ```bash + dream> "waterfall and rainbow" -I./vacation-photo.png -M./vacation-mask.png -W640 -H480 --fit + ``` -inpainting accepts all the arguments used for txt2img and img2img, as well as the --mask (-M) + This will do the same thing as img2img, but image alterations will only occur within transparent + areas defined by the mask file specified by `-M`. You may also supply just a single initial image with + the areas to overpaint made transparent, but you must be careful not to destroy the pixels + underneath when you create the transparent areas. See [Inpainting](./INPAINTING.md) for details. + +Inpainting accepts all the arguments used for txt2img and img2img, as well as the `--mask` (`-M`) argument: -| Argument | Shortcut | Default | Description | -| ------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------ | -| --init_mask | -M | None | Path to an image the same size as the initial_image, with areas for inpainting made transparent. | +| Argument | Shortcut | Default | Description | +| ----------------------------------------- | ---------- | ------- | ------------------------------------------------------------------------------------------------ | +| `--init_mask ` | `-M` | `None` | Path to an image the same size as the initial_image, with areas for inpainting made transparent. | ## Command-line editing and completion If you are on a Macintosh or Linux machine, the command-line offers convenient history tracking, editing, and command completion. -- To scroll through previous commands and potentially edit/reuse them, use the up and down cursor - keys. -- To edit the current command, use the left and right cursor keys to position the cursor, and then - backspace, delete or insert characters. -- To move to the very beginning of the command, type CTRL-A (or command-A on the Mac) -- To move to the end of the command, type CTRL-E. +- To scroll through previous commands and potentially edit/reuse them, use the ++up++ and ++down++ + cursor keys. +- To edit the current command, use the ++left++ and ++right++ cursor keys to position the cursor, + and then ++backspace++, ++delete++ or ++insert++ characters. +- To move to the very beginning of the command, type ++ctrl+a++ (or ++command+a++ on the Mac) +- To move to the end of the command, type ++ctrl+e++. - To cut a section of the command, position the cursor where you want to start cutting and type - CTRL-K. -- To paste a cut section back in, position the cursor where you want to paste, and type CTRL-Y + ++ctrl+k++. +- To paste a cut section back in, position the cursor where you want to paste, and type ++ctrl+y++ -Windows users can get similar, but more limited, functionality if they launch dream.py with the +Windows users can get similar, but more limited, functionality if they launch `dream.py` with the "winpty" program: -``` -> winpty python scripts\dream.py +```batch +winpty python scripts\dream.py ``` -On the Mac and Linux platforms, when you exit dream.py, the last 1000 lines of your command-line -history will be saved. When you restart dream.py, you can access the saved history using the -up-arrow key. +On the Mac and Linux platforms, when you exit `dream.py`, the last 1000 lines of your command-line +history will be saved. When you restart `dream.py`, you can access the saved history using the +++up++ key. In addition, limited command-line completion is installed. In various contexts, you can start typing your command and press tab. A list of potential completions will be presented to you. You can then type a little more, hit tab again, and eventually autocomplete what you want. When specifying file paths using the one-letter shortcuts, the CLI will attempt to complete -pathnames for you. This is most handy for the -I (init image) and -M (init mask) paths. To initiate -completion, start the path with a slash ("/") or "./". For example: +pathnames for you. This is most handy for the `-I` (init image) and `-M` (init mask) paths. To +initiate completion, start the path with a slash `/` or `./`, for example: -``` -dream> zebra with a mustache -I./test-pictures +```bash +dream> "zebra with a mustache" -I./test-pictures -I./test-pictures/Lincoln-and-Parrot.png -I./test-pictures/zebra.jpg -I./test-pictures/madonna.png -I./test-pictures/bad-sketch.png -I./test-pictures/man_with_eagle/ ``` -You can then type "z", hit tab again, and it will autofill to "zebra.jpg". +You can then type ++z++, hit ++tab++ again, and it will autofill to `zebra.jpg`. More text completion features (such as autocompleting seeds) are on their way. diff --git a/docs/features/EMBIGGEN.md b/docs/features/EMBIGGEN.md index c879102160..aecebf04e8 100644 --- a/docs/features/EMBIGGEN.md +++ b/docs/features/EMBIGGEN.md @@ -1,4 +1,10 @@ -# **Embiggen -- upscale your images on limited memory machines** +--- +title: Embiggen +--- + +# :material-loupe: Embiggen + +**upscale your images on limited memory machines** GFPGAN and Real-ESRGAN are both memory intensive. In order to avoid crashes and memory overloads during the Stable Diffusion process, @@ -16,7 +22,7 @@ face restore a particular generated image, pass it again with the same prompt and generated seed along with the `-U` and `-G` prompt arguments to perform those actions. -## Embiggen +## Embiggen If you wanted to be able to do more (pixels) without running out of VRAM, or you want to upscale with details that couldn't possibly appear @@ -37,7 +43,7 @@ it's similar to that, except it can work up to an arbitrarily large size has extra logic to re-run any number of the tile sub-sections of the image if for example a small part of a huge run got messed up. -**Usage** +## Usage `-embiggen ` @@ -94,12 +100,12 @@ Tiles are numbered starting with one, and left-to-right, top-to-bottom. So, if you are generating a 3x3 tiled image, the middle row would be `4 5 6`. -**Example Usage** +## Example Usage Running Embiggen with 512x512 tiles on an existing image, scaling up by a factor of 2.5x; and doing the same again (default ESRGAN strength is 0.75, default overlap between tiles is 0.25): -``` +```bash dream > a photo of a forest at sunset -s 100 -W 512 -H 512 -I outputs/forest.png -f 0.4 -embiggen 2.5 dream > a photo of a forest at sunset -s 100 -W 512 -H 512 -I outputs/forest.png -f 0.4 -embiggen 2.5 0.75 0.25 ``` @@ -111,23 +117,23 @@ If there weren't enough clouds in the sky of that forest you just made 512x512 tiles with 0.25 overlaps wide) we can replace that top row of tiles: -``` +```bash dream> a photo of puffy clouds over a forest at sunset -s 100 -W 512 -H 512 -I outputs/000002.seed.png -f 0.5 -embiggen_tiles 1 2 3 ``` -**Note** +!!! note -Because the same prompt is used on all the tiled images, and the model -doesn't have the context of anything outside the tile being run - it -can end up creating repeated pattern (also called 'motifs') across all -the tiles based on that prompt. The best way to combat this is -lowering the `--strength` (`-f`) to stay more true to the init image, -and increasing the number of steps so there is more compute-time to -create the detail. Anecdotally `--strength` 0.35-0.45 works pretty -well on most things. It may also work great in some examples even with -the `--strength` set high for patterns, landscapes, or subjects that -are more abstract. Because this is (relatively) fast, you can also -always create a few Embiggen'ed images and manually composite them to -preserve the best parts from each. + Because the same prompt is used on all the tiled images, and the model + doesn't have the context of anything outside the tile being run - it + can end up creating repeated pattern (also called 'motifs') across all + the tiles based on that prompt. The best way to combat this is + lowering the `--strength` (`-f`) to stay more true to the init image, + and increasing the number of steps so there is more compute-time to + create the detail. Anecdotally `--strength` 0.35-0.45 works pretty + well on most things. It may also work great in some examples even with + the `--strength` set high for patterns, landscapes, or subjects that + are more abstract. Because this is (relatively) fast, you can also + always create a few Embiggen'ed images and manually composite them to + preserve the best parts from each. -Author: [Travco](https://github.com/travco) \ No newline at end of file +Author: [Travco](https://github.com/travco) diff --git a/docs/features/IMG2IMG.md b/docs/features/IMG2IMG.md index eeaecbf981..e61f365c01 100644 --- a/docs/features/IMG2IMG.md +++ b/docs/features/IMG2IMG.md @@ -2,7 +2,8 @@ title: Image-to-Image --- -## **IMG2IMG** +# :material-image-multiple: **IMG2IMG** + This script also provides an `img2img` feature that lets you seed your creations with an initial drawing or photo. This is a really cool feature that tells stable diffusion to build the prompt on top of the image you provide, preserving the original's basic shape and layout. To use it, provide diff --git a/docs/features/INPAINTING.md b/docs/features/INPAINTING.md index 317dc99698..d5857d7cdd 100644 --- a/docs/features/INPAINTING.md +++ b/docs/features/INPAINTING.md @@ -2,6 +2,8 @@ title: Inpainting --- +# :octicons-paintbrush-16: Inpainting + ## **Creating Transparent Regions for Inpainting** Inpainting is really cool. To do it, you start with an initial image and use a photoeditor to make @@ -26,6 +28,8 @@ dream> "man with cat on shoulder" -I./images/man.png -M./images/man-transparent. We are hoping to get rid of the need for this workaround in an upcoming release. +--- + ## Recipe for GIMP [GIMP](https://www.gimp.org/) is a popular Linux photoediting tool. @@ -34,7 +38,7 @@ We are hoping to get rid of the need for this workaround in an upcoming release. 2. Layer->Transparency->Add Alpha Channel 3. Use lasoo tool to select region to mask 4. Choose Select -> Float to create a floating selection -5. Open the Layers toolbar (^L) and select "Floating Selection" +5. Open the Layers toolbar (++ctrl+l++) and select "Floating Selection" 6. Set opacity to 0% 7. Export as PNG 8. In the export dialogue, Make sure the "Save colour values from @@ -44,37 +48,41 @@ We are hoping to get rid of the need for this workaround in an upcoming release. ## Recipe for Adobe Photoshop 1. Open image in Photoshop -

- -

+ +
+ ![step1](../assets/step1.png) +
2. Use any of the selection tools (Marquee, Lasso, or Wand) to select the area you desire to inpaint. -

- -

-3. Because we'll be applying a mask over the area we want to preserve, you should now select the inverse by using the Shift + Ctrl + I shortcut, or right clicking and using the "Select Inverse" option. +
+ ![step2](../assets/step2.png) +
-4. You'll now create a mask by selecting the image layer, and Masking the selection. Make sure that you don't delete any of the underlying image, or your inpainting results will be dramatically impacted. -

- -

+3. Because we'll be applying a mask over the area we want to preserve, you should now select the inverse by using the ++shift+ctrl+i++ shortcut, or right clicking and using the "Select Inverse" option. + +4. You'll now create a mask by selecting the image layer, and Masking the selection. Make sure that you don't delete any of the undrlying image, or your inpainting results will be dramatically impacted. + +
+ ![step4](../assets/step4.png) +
5. Make sure to hide any background layers that are present. You should see the mask applied to your image layer, and the image on your canvas should display the checkered background. -

- -

-

- -

+
+ ![step5](../assets/step5.png) +
-6. Save the image as a transparent PNG by using the "Save a Copy" option in the File menu, or using the Alt + Ctrl + S keyboard shortcut. +6. Save the image as a transparent PNG by using the "Save a Copy" option in the File menu, or using the Alt + Ctrl + S keyboard shortcut + +
+ ![step6](../assets/step6.png) +
7. After following the inpainting instructions above (either through the CLI or the Web UI), marvel at your newfound ability to selectively dream. Lookin' good! -

- -

-8. In the export dialogue, Make sure the "Save colour values from transparent pixels" checkbox is - selected. +
+ ![step7](../assets/step7.png) +
+ +8. In the export dialogue, Make sure the "Save colour values from transparent pixels" checkbox is selected. diff --git a/docs/features/OTHER.md b/docs/features/OTHER.md index 019ed35b70..5faa1d32d0 100644 --- a/docs/features/OTHER.md +++ b/docs/features/OTHER.md @@ -2,6 +2,8 @@ title: Others --- +# :fontawesome-regular-share-from-square: Others + ## **Google Colab** Stable Diffusion AI Notebook: +![step1](../assets/negative_prompt_walkthru/step1.png) + That image has a woman, so if we want the horse without a rider, we can influence the image not to have a woman by putting [woman] in the prompt, like this: -```bash -"A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180 -``` +`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180` -![step2](../assets/variation_walkthru/step2.png) +
+![step2](../assets/negative_prompt_walkthru/step2.png) +
That's nice - but say we also don't want the image to be quite so blue. We can add "blue" to the list of negative prompts, so it's now [woman blue]: -```bash -"A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180 -``` - -![step3](../assets/variation_walkthru/step3.png) +`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180` +
+![step3](../assets/negative_prompt_walkthru/step3.png) +
Getting close - but there's no sense in having a saddle when our horse doesn't have a rider, so we'll add one more negative prompt: [woman blue saddle]. -```bash -"A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue saddle]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180 -``` +`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue saddle]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180` -![step4](../assets/variation_walkthru/step4.png) +
+![step4](../assets/negative_prompt_walkthru/step4.png) +
+!!! notes "Notes about this feature:" -Notes about this feature: - -* The only requirement for words to be ignored is that they are in between a pair of square brackets. -* You can provide multiple words within the same bracket. -* You can provide multiple brackets with multiple words in different places of your prompt. That works just fine. -* To improve typical anatomy problems, you can add negative prompts like [bad anatomy, extra legs, extra arms, extra fingers, poorly drawn hands, poorly drawn feet, disfigured, out of frame, tiling, bad art, deformed, mutated]. \ No newline at end of file + * The only requirement for words to be ignored is that they are in between a pair of square brackets. + * You can provide multiple words within the same bracket. + * You can provide multiple brackets with multiple words in different places of your prompt. That works just fine. + * To improve typical anatomy problems, you can add negative prompts like `[bad anatomy, extra legs, extra arms, extra fingers, poorly drawn hands, poorly drawn feet, disfigured, out of frame, tiling, bad art, deformed, mutated]`. diff --git a/docs/features/TEXTUAL_INVERSION.md b/docs/features/TEXTUAL_INVERSION.md index b8dbc21192..50532968a8 100644 --- a/docs/features/TEXTUAL_INVERSION.md +++ b/docs/features/TEXTUAL_INVERSION.md @@ -2,6 +2,8 @@ title: TEXTUAL_INVERSION --- +# :material-file-document-plus-outline: TEXTUAL_INVERSION + ## **Personalizing Text-to-Image Generation** You may personalize the generated images to provide your own styles or objects @@ -39,7 +41,7 @@ and one with the init word provided. On a RTX3090, the process for SD will take ~1h @1.6 iterations/sec. -!!! Info _Note_ +!!! note According to the associated paper, the optimal number of images is 3-5. Your model may not converge if you use more images than @@ -57,9 +59,7 @@ Once the model is trained, specify the trained .pt or .bin file when starting dream using ```bash -python3 ./scripts/dream.py \ - --embedding_path /path/to/embedding.pt \ - --full_precision +python3 ./scripts/dream.py --embedding_path /path/to/embedding.pt ``` Then, to utilize your subject at the dream prompt diff --git a/docs/features/UPSCALE.md b/docs/features/UPSCALE.md index 259b569e88..db5649ecdb 100644 --- a/docs/features/UPSCALE.md +++ b/docs/features/UPSCALE.md @@ -4,14 +4,16 @@ title: Upscale ## **Intro** -The script provides the ability to restore faces and upscale. +The script provides the ability to restore faces and upscale. You can apply these operations +at the time you generate the images, or at any time to a previously-generated PNG file, using +the [!fix](#Fixing Previously-Generated Images) command. -You can enable these features by passing `--restore` and `--esrgan` to your launch script to enable -face restoration modules and upscaling modules respectively. +# :material-image-size-select-large: Upscale -## **GFPGAN and Real-ESRGAN Support** +## **Face Fixing** -The default face restoration module is GFPGAN and the default upscaling module is ESRGAN. +The default face restoration module is GFPGAN. The default upscale is Real-ESRGAN. For an alternative +face restoration module, see [CodeFormer Support] below. As of version 1.14, environment.yaml will install the Real-ESRGAN package into the standard install location for python packages, and will put GFPGAN into a subdirectory of "src" in the @@ -36,11 +38,13 @@ this package which asked you to install GFPGAN in a sibling directory, you may u `--gfpgan_dir` argument with `dream.py` to set a custom path to your GFPGAN directory. _There are other GFPGAN related boot arguments if you wish to customize further._ -**Note: Internet connection needed:** Users whose GPU machines are isolated from the Internet (e.g. -on a University cluster) should be aware that the first time you run dream.py with GFPGAN and -Real-ESRGAN turned on, it will try to download model files from the Internet. To rectify this, you -may run `python3 scripts/preload_models.py` after you have installed GFPGAN and all its -dependencies. +!!! warning "Internet connection needed" + + Users whose GPU machines are isolated from the Internet (e.g. + on a University cluster) should be aware that the first time you run dream.py with GFPGAN and + Real-ESRGAN turned on, it will try to download model files from the Internet. To rectify this, you + may run `python3 scripts/preload_models.py` after you have installed GFPGAN and all its + dependencies. ## **Usage** @@ -89,16 +93,16 @@ This also works with img2img: dream> a man wearing a pineapple hat -I path/to/your/file.png -U 2 0.5 -G 0.6 ``` -### **Note** +!!! note -GFPGAN and Real-ESRGAN are both memory intensive. In order to avoid crashes and memory overloads -during the Stable Diffusion process, these effects are applied after Stable Diffusion has completed -its work. + GFPGAN and Real-ESRGAN are both memory intensive. In order to avoid crashes and memory overloads + during the Stable Diffusion process, these effects are applied after Stable Diffusion has completed + its work. -In single image generations, you will see the output right away but when you are using multiple -iterations, the images will first be generated and then upscaled and face restored after that -process is complete. While the image generation is taking place, you will still be able to preview -the base images. + In single image generations, you will see the output right away but when you are using multiple + iterations, the images will first be generated and then upscaled and face restored after that + process is complete. While the image generation is taking place, you will still be able to preview + the base images. If you wish to stop during the image generation but want to upscale or face restore a particular generated image, pass it again with the same prompt and generated seed along with the `-U` and `-G` @@ -139,3 +143,22 @@ that is the best restoration possible. This may deviate slightly from the origin excellent option to use in situations when there is very little facial data to work with. ` -G 1.0 -ft codeformer -cf 0.1` + +## Fixing Previously-Generated Images + +It is easy to apply face restoration and/or upscaling to any previously-generated file. Just use the +syntax `!fix path/to/file.png `. For example, to apply GFPGAN at strength 0.8 and upscale 2X +for a file named `./outputs/img-samples/000044.2945021133.png`, just run: + +~~~~ +dream> !fix ./outputs/img-samples/000044.2945021133.png -G 0.8 -U 2 +~~~~ + +A new file named `000044.2945021133.fixed.png` will be created in the output directory. Note that +the `!fix` command does not replace the original file, unlike the behavior at generate time. + +**Disabling:** + +If, for some reason, you do not wish to load the GFPGAN and/or ESRGAN libraries, you can disable them +on the dream.py command line with the `--no_restore` and `--no_upscale` options, respectively. + diff --git a/docs/features/VARIATIONS.md b/docs/features/VARIATIONS.md index cff6a1c3d6..d07dc13c76 100644 --- a/docs/features/VARIATIONS.md +++ b/docs/features/VARIATIONS.md @@ -2,6 +2,10 @@ title: Variations --- +# :material-tune-variant: Variations + +## Intro + Release 1.13 of SD-Dream adds support for image variations. You are able to do the following: @@ -29,7 +33,7 @@ This will be indicated as `prompt` in the examples below. First we let SD create a series of images in the usual way, in this case requesting six iterations: -``` +```bash dream> lucy lawless as xena, warrior princess, character portrait, high resolution -n6 ... Outputs: @@ -41,9 +45,10 @@ Outputs: ./outputs/Xena/000001.3357757885.png: "prompt" -s50 -W512 -H512 -C7.5 -Ak_lms -S3357757885 ``` -The one with seed 3357757885 looks nice: - -![var1](../assets/variation_walkthru/000001.3357757885.png) +
+ ![var1](../assets/variation_walkthru/000001.3357757885.png) +
Seed 3357757885 looks nice
+
--- @@ -75,15 +80,21 @@ used to generate it. This gives us a series of closely-related variations, including the two shown here. -![var2](../assets/variation_walkthru/000002.3647897225.png) +
+ ![var2](../assets/variation_walkthru/000002.3647897225.png) +
subseed 3647897225
+
-![var3](../assets/variation_walkthru/000002.1614299449.png) +
+ ![var3](../assets/variation_walkthru/000002.1614299449.png) +
subseed 1614299449
+
I like the expression on Xena's face in the first one (subseed 3647897225), and the armor on her shoulder in the second one (subseed 1614299449). Can we combine them to get the best of both worlds? -We combine the two variations using `-V` (--with_variations). Again, we must +We combine the two variations using `-V` (`--with_variations`). Again, we must provide the seed for the originally-chosen image in order for this to work. ```bash @@ -95,7 +106,9 @@ Outputs: Here we are providing equal weights (0.1 and 0.1) for both the subseeds. The resulting image is close, but not exactly what I wanted: -![var4](../assets/variation_walkthru/000003.1614299449.png) +
+ ![var4](../assets/variation_walkthru/000003.1614299449.png) +
We could either try combining the images with different weights, or we can generate more variations around the almost-but-not-quite image. We do the @@ -116,7 +129,10 @@ Outputs: This produces six images, all slight variations on the combination of the chosen two images. Here's the one I like best: -![var5](../assets/variation_walkthru/000004.3747154981.png) +
+ ![var5](../assets/variation_walkthru/000004.3747154981.png) +
000004.3747154981.png
+
As you can see, this is a very powerful tool, which when combined with subprompt weighting, gives you great control over the content and quality of your diff --git a/docs/features/WEB.md b/docs/features/WEB.md index 1bbf5f3b55..833b18cdfc 100644 --- a/docs/features/WEB.md +++ b/docs/features/WEB.md @@ -2,8 +2,10 @@ title: Barebones Web Server --- +# :material-web: Barebones Web Server + As of version 1.10, this distribution comes with a bare bones web server (see -screenshot). To use it, run the `dream.py` script by adding the `**--web**` +screenshot). To use it, run the `dream.py` script by adding the `--web` option. ```bash diff --git a/docs/help/TROUBLESHOOT.md b/docs/help/TROUBLESHOOT.md index 6458d8ceb9..f5dcfe2c1c 100644 --- a/docs/help/TROUBLESHOOT.md +++ b/docs/help/TROUBLESHOOT.md @@ -2,6 +2,8 @@ title: F.A.Q. --- +# :material-frequently-asked-questions: F.A.Q. + ## **Frequently-Asked-Questions** Here are a few common installation problems and their solutions. Often these are caused by diff --git a/docs/index.md b/docs/index.md index bdde3cabd7..c356c2cee5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,102 +1,106 @@ --- title: Home +template: main.html --- +
-

Stable Diffusion Dream Script

+# :material-script-text-outline: Stable Diffusion Dream Script -

- -

+![project logo](assets/logo.png) -

- last-commit - stars -
- issues - pull-requests -

+[![discord badge]][discord link] + +[![latest release badge]][latest release link] [![github stars badge]][github stars link] [![github forks badge]][github forks link] + +[![CI checks on main badge]][CI checks on main link] [![CI checks on dev badge]][CI checks on dev link] [![latest commit to dev badge]][latest commit to dev link] + +[![github open issues badge]][github open issues link] [![github open prs badge]][github open prs link] + +[CI checks on dev badge]: https://flat.badgen.net/github/checks/lstein/stable-diffusion/development?label=CI%20status%20on%20dev&cache=900&icon=github +[CI checks on dev link]: https://github.com/lstein/stable-diffusion/actions?query=branch%3Adevelopment +[CI checks on main badge]: https://flat.badgen.net/github/checks/lstein/stable-diffusion/main?label=CI%20status%20on%20main&cache=900&icon=github +[CI checks on main link]: https://github.com/lstein/stable-diffusion/actions/workflows/test-dream-conda.yml +[discord badge]: https://flat.badgen.net/discord/members/htRgbc7e?icon=discord +[discord link]: https://discord.com/invite/htRgbc7e +[github forks badge]: https://flat.badgen.net/github/forks/lstein/stable-diffusion?icon=github +[github forks link]: https://useful-forks.github.io/?repo=lstein%2Fstable-diffusion +[github open issues badge]: https://flat.badgen.net/github/open-issues/lstein/stable-diffusion?icon=github +[github open issues link]: https://github.com/lstein/stable-diffusion/issues?q=is%3Aissue+is%3Aopen +[github open prs badge]: https://flat.badgen.net/github/open-prs/lstein/stable-diffusion?icon=github +[github open prs link]: https://github.com/lstein/stable-diffusion/pulls?q=is%3Apr+is%3Aopen +[github stars badge]: https://flat.badgen.net/github/stars/lstein/stable-diffusion?icon=github +[github stars link]: https://github.com/lstein/stable-diffusion/stargazers +[latest commit to dev badge]: https://flat.badgen.net/github/last-commit/lstein/stable-diffusion/development?icon=github&color=yellow&label=last%20dev%20commit&cache=900 +[latest commit to dev link]: https://github.com/lstein/stable-diffusion/commits/development +[latest release badge]: https://flat.badgen.net/github/release/lstein/stable-diffusion/development?icon=github +[latest release link]: https://github.com/lstein/stable-diffusion/releases + +
This is a fork of [CompVis/stable-diffusion](https://github.com/CompVis/stable-diffusion), the open source text-to-image generator. It provides a streamlined process with various new features and options to aid the image generation process. It runs on Windows, Mac and Linux machines, and runs on GPU cards with as little as 4 GB or RAM. -_Note: This fork is rapidly evolving. Please use the -[Issues](https://github.com/lstein/stable-diffusion/issues) tab to report bugs and make feature -requests. Be sure to use the provided templates. They will help aid diagnose issues faster._ +!!! note -## Installation + This fork is rapidly evolving. Please use the + [Issues](https://github.com/lstein/stable-diffusion/issues) tab to report bugs and make feature + requests. Be sure to use the provided templates. They will help aid diagnose issues faster. + +## :octicons-package-dependencies-24: Installation This fork is supported across multiple platforms. You can find individual installation instructions below. -- [Linux](installation/INSTALL_LINUX.md) -- [Windows](installation/INSTALL_WINDOWS.md) -- [Macintosh](installation/INSTALL_MAC.md) +- :fontawesome-brands-linux: [Linux](installation/INSTALL_LINUX.md) +- :fontawesome-brands-windows: [Windows](installation/INSTALL_WINDOWS.md) +- :fontawesome-brands-apple: [Macintosh](installation/INSTALL_MAC.md) -## Hardware Requirements +## :fontawesome-solid-computer: Hardware Requirements -### System +### :octicons-cpu-24: System You wil need one of the following: -- An NVIDIA-based graphics card with 4 GB or more VRAM memory. -- An Apple computer with an M1 chip. +- :simple-nvidia: An NVIDIA-based graphics card with 4 GB or more VRAM memory. +- :fontawesome-brands-apple: An Apple computer with an M1 chip. -### Memory +### :fontawesome-solid-memory: Memory - At least 12 GB Main Memory RAM. -### Disk +### :fontawesome-regular-hard-drive: Disk - At least 6 GB of free disk space for the machine learning model, Python, and all its dependencies. -### Note +!!! note -If you are have a Nvidia 10xx series card (e.g. the 1080ti), please run the dream script in -full-precision mode as shown below. + If you are have a Nvidia 10xx series card (e.g. the 1080ti), please run the dream script in + full-precision mode as shown below. -Similarly, specify full-precision mode on Apple M1 hardware. + Similarly, specify full-precision mode on Apple M1 hardware. -To run in full-precision mode, start `dream.py` with the `--full_precision` flag: + To run in full-precision mode, start `dream.py` with the `--full_precision` flag: -```bash -(ldm) ~/stable-diffusion$ python scripts/dream.py --full_precision -``` + ```bash + (ldm) ~/stable-diffusion$ python scripts/dream.py --full_precision + ``` +## :octicons-log-16: Latest Changes -## Features +### vNEXT (TODO 2022) -### Major Features - -- [Interactive Command Line Interface](features/CLI.md) -- [Image To Image](features/IMG2IMG.md) -- [Inpainting Support](features/INPAINTING.md) -- [GFPGAN and Real-ESRGAN Support](features/UPSCALE.md) -- [Seamless Tiling](features/OTHER.md#seamless-tiling) -- [Google Colab](features/OTHER.md#google-colab) -- [Web Server](features/WEB.md) -- [Reading Prompts From File](features/OTHER.md#reading-prompts-from-a-file) -- [Shortcut: Reusing Seeds](features/OTHER.md#shortcuts-reusing-seeds) -- [Weighted Prompts](features/OTHER.md#weighted-prompts) -- [Variations](features/VARIATIONS.md) -- [Personalizing Text-to-Image Generation](features/TEXTUAL_INVERSION.md) -- [Simplified API for text to image generation](features/OTHER.md#simplified-api) - -### Other Features - -- [Creating Transparent Regions for Inpainting](features/INPAINTING.md#creating-transparent-regions-for-inpainting) -- [Preload Models](features/OTHER.md#preload-models) - -## Latest Changes + - Deprecated `--full_precision` / `-F`. Simply omit it and `dream.py` will auto + configure. To switch away from auto use the new flag like `--precision=float32`. ### v1.14 (11 September 2022) @@ -127,12 +131,12 @@ To run in full-precision mode, start `dream.py` with the `--full_precision` flag For older changelogs, please visit the **[CHANGELOG](features/CHANGELOG.md)**. -## Troubleshooting +## :material-target: Troubleshooting -Please check out our **[Q&A](help/TROUBLESHOOT.md)** to get solutions for common installation +Please check out our **[:material-frequently-asked-questions: Q&A](help/TROUBLESHOOT.md)** to get solutions for common installation problems and other issues. -## Contributing +## :octicons-repo-push-24: Contributing Anyone who wishes to contribute to this project, whether documentation, features, bug fixes, code cleanup, testing, or code reviews, is very much encouraged to do so. If you are unfamiliar with how @@ -144,13 +148,13 @@ important thing is to **make your pull request against the "development" branch* "main". This will help keep public breakage to a minimum and will allow you to propose more radical changes. -## Contributors +## :octicons-person-24: Contributors This fork is a combined effort of various people from across the world. [Check out the list of all these amazing people](other/CONTRIBUTORS.md). We thank them for their time, hard work and effort. -## Support +## :octicons-question-24: Support For support, please use this repository's GitHub Issues tracking service. Feel free to send me an email if you use and like the script. @@ -158,7 +162,7 @@ email if you use and like the script. Original portions of the software are Copyright (c) 2020 [Lincoln D. Stein](https://github.com/lstein) -## Further Reading +## :octicons-book-24: Further Reading Please see the original README for more information on this software and underlying algorithm, located in the file [README-CompViz.md](other/README-CompViz.md). diff --git a/docs/installation/INSTALL_DOCKER.md b/docs/installation/INSTALL_DOCKER.md new file mode 100644 index 0000000000..34974e0f2f --- /dev/null +++ b/docs/installation/INSTALL_DOCKER.md @@ -0,0 +1,183 @@ +# Before you begin + +- For end users: Install Stable Diffusion locally using the instructions for your OS. +- For developers: For container-related development tasks or for enabling easy deployment to other environments (on-premises or cloud), follow these instructions. For general use, install locally to leverage your machine's GPU. + +# Why containers? + +They provide a flexible, reliable way to build and deploy Stable Diffusion. You'll also use a Docker volume to store the largest model files and image outputs as a first step in decoupling storage and compute. Future enhancements can do this for other assets. See [Processes](https://12factor.net/processes) under the Twelve-Factor App methodology for details on why running applications in such a stateless fashion is important. + +You can specify the target platform when building the image and running the container. You'll also need to specify the Stable Diffusion requirements file that matches the container's OS and the architecture it will run on. + +Developers on Apple silicon (M1/M2): You [can't access your GPU cores from Docker containers](https://github.com/pytorch/pytorch/issues/81224) and performance is reduced compared with running it directly on macOS but for development purposes it's fine. Once you're done with development tasks on your laptop you can build for the target platform and architecture and deploy to another environment with NVIDIA GPUs on-premises or in the cloud. + +# Installation on a Linux container + +## Prerequisites + +### Get the data files + +Go to [Hugging Face](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original), and click "Access repository" to Download the model file ```sd-v1-4.ckpt``` (~4 GB) to ```~/Downloads```. You'll need to create an account but it's quick and free. + +Also download the face restoration model. +```Shell +cd ~/Downloads +wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth +``` + +### Install [Docker](https://github.com/santisbon/guides#docker) +On the Docker Desktop app, go to Preferences, Resources, Advanced. Increase the CPUs and Memory to avoid this [Issue](https://github.com/lstein/stable-diffusion/issues/342). You may need to increase Swap and Disk image size too. + +## Setup + +Set the fork you want to use and other variables. +```Shell +TAG_STABLE_DIFFUSION="santisbon/stable-diffusion" +PLATFORM="linux/arm64" +GITHUB_STABLE_DIFFUSION="-b orig-gfpgan https://github.com/santisbon/stable-diffusion.git" +REQS_STABLE_DIFFUSION="requirements-linux-arm64.txt" +CONDA_SUBDIR="osx-arm64" + +echo $TAG_STABLE_DIFFUSION +echo $PLATFORM +echo $GITHUB_STABLE_DIFFUSION +echo $REQS_STABLE_DIFFUSION +echo $CONDA_SUBDIR +``` + +Create a Docker volume for the downloaded model files. +```Shell +docker volume create my-vol +``` + +Copy the data files to the Docker volume using a lightweight Linux container. We'll need the models at run time. You just need to create the container with the mountpoint; no need to run this dummy container. +```Shell +cd ~/Downloads # or wherever you saved the files + +docker create --platform $PLATFORM --name dummy --mount source=my-vol,target=/data alpine + +docker cp sd-v1-4.ckpt dummy:/data +docker cp GFPGANv1.3.pth dummy:/data +``` + +Get the repo and download the Miniconda installer (we'll need it at build time). Replace the URL with the version matching your container OS and the architecture it will run on. +```Shell +cd ~ +git clone $GITHUB_STABLE_DIFFUSION + +cd stable-diffusion/docker-build +chmod +x entrypoint.sh +wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh -O anaconda.sh && chmod +x anaconda.sh +``` + +Build the Docker image. Give it any tag ```-t``` that you want. +Choose the Linux container's host platform: x86-64/Intel is ```amd64```. Apple silicon is ```arm64```. If deploying the container to the cloud to leverage powerful GPU instances you'll be on amd64 hardware but if you're just trying this out locally on Apple silicon choose arm64. +The application uses libraries that need to match the host environment so use the appropriate requirements file. +Tip: Check that your shell session has the env variables set above. +```Shell +docker build -t $TAG_STABLE_DIFFUSION \ +--platform $PLATFORM \ +--build-arg gsd=$GITHUB_STABLE_DIFFUSION \ +--build-arg rsd=$REQS_STABLE_DIFFUSION \ +--build-arg cs=$CONDA_SUBDIR \ +. +``` + +Run a container using your built image. +Tip: Make sure you've created and populated the Docker volume (above). +```Shell +docker run -it \ +--rm \ +--platform $PLATFORM \ +--name stable-diffusion \ +--hostname stable-diffusion \ +--mount source=my-vol,target=/data \ +$TAG_STABLE_DIFFUSION +``` + +# Usage (time to have fun) + +## Startup +If you're on a **Linux container** the ```dream``` script is **automatically started** and the output dir set to the Docker volume you created earlier. + +If you're **directly on macOS follow these startup instructions**. +With the Conda environment activated (```conda activate ldm```), run the interactive interface that combines the functionality of the original scripts ```txt2img``` and ```img2img```: +Use the more accurate but VRAM-intensive full precision math because half-precision requires autocast and won't work. +By default the images are saved in ```outputs/img-samples/```. +```Shell +python3 scripts/dream.py --full_precision +``` + +You'll get the script's prompt. You can see available options or quit. +```Shell +dream> -h +dream> q +``` + +## Text to Image +For quick (but bad) image results test with 5 steps (default 50) and 1 sample image. This will let you know that everything is set up correctly. +Then increase steps to 100 or more for good (but slower) results. +The prompt can be in quotes or not. +```Shell +dream> The hulk fighting with sheldon cooper -s5 -n1 +dream> "woman closeup highly detailed" -s 150 +# Reuse previous seed and apply face restoration +dream> "woman closeup highly detailed" --steps 150 --seed -1 -G 0.75 +``` + +You'll need to experiment to see if face restoration is making it better or worse for your specific prompt. + +If you're on a container the output is set to the Docker volume. You can copy it wherever you want. +You can download it from the Docker Desktop app, Volumes, my-vol, data. +Or you can copy it from your Mac terminal. Keep in mind ```docker cp``` can't expand ```*.png``` so you'll need to specify the image file name. + +On your host Mac (you can use the name of any container that mounted the volume): +```Shell +docker cp dummy:/data/000001.928403745.png /Users//Pictures +``` + +## Image to Image +You can also do text-guided image-to-image translation. For example, turning a sketch into a detailed drawing. + +```strength``` is a value between 0.0 and 1.0 that controls the amount of noise that is added to the input image. Values that approach 1.0 allow for lots of variations but will also produce images that are not semantically consistent with the input. 0.0 preserves image exactly, 1.0 replaces it completely. + +Make sure your input image size dimensions are multiples of 64 e.g. 512x512. Otherwise you'll get ```Error: product of dimension sizes > 2**31'```. If you still get the error [try a different size](https://support.apple.com/guide/preview/resize-rotate-or-flip-an-image-prvw2015/mac#:~:text=image's%20file%20size-,In%20the%20Preview%20app%20on%20your%20Mac%2C%20open%20the%20file,is%20shown%20at%20the%20bottom.) like 512x256. + +If you're on a Docker container, copy your input image into the Docker volume +```Shell +docker cp /Users//Pictures/sketch-mountains-input.jpg dummy:/data/ +``` + +Try it out generating an image (or more). The ```dream``` script needs absolute paths to find the image so don't use ```~```. + +If you're on your Mac +```Shell +dream> "A fantasy landscape, trending on artstation" -I /Users//Pictures/sketch-mountains-input.jpg --strength 0.75 --steps 100 -n4 +``` +If you're on a Linux container on your Mac +```Shell +dream> "A fantasy landscape, trending on artstation" -I /data/sketch-mountains-input.jpg --strength 0.75 --steps 50 -n1 +``` + +## Web Interface +You can use the ```dream``` script with a graphical web interface. Start the web server with: +```Shell +python3 scripts/dream.py --full_precision --web +``` +If it's running on your Mac point your Mac web browser to http://127.0.0.1:9090 + +Press Control-C at the command line to stop the web server. + +## Notes + +Some text you can add at the end of the prompt to make it very pretty: +```Shell +cinematic photo, highly detailed, cinematic lighting, ultra-detailed, ultrarealistic, photorealism, Octane Rendering, cyberpunk lights, Hyper Detail, 8K, HD, Unreal Engine, V-Ray, full hd, cyberpunk, abstract, 3d octane render + 4k UHD + immense detail + dramatic lighting + well lit + black, purple, blue, pink, cerulean, teal, metallic colours, + fine details, ultra photoreal, photographic, concept art, cinematic composition, rule of thirds, mysterious, eerie, photorealism, breathtaking detailed, painting art deco pattern, by hsiao, ron cheng, john james audubon, bizarre compositions, exquisite detail, extremely moody lighting, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli, akihiko yoshida +``` + +The original scripts should work as well. +```Shell +python3 scripts/orig_scripts/txt2img.py --help +python3 scripts/orig_scripts/txt2img.py --ddim_steps 100 --n_iter 1 --n_samples 1 --plms --prompt "new born baby kitten. Hyper Detail, Octane Rendering, Unreal Engine, V-Ray" +python3 scripts/orig_scripts/txt2img.py --ddim_steps 5 --n_iter 1 --n_samples 1 --plms --prompt "ocean" # or --klms +``` \ No newline at end of file diff --git a/docs/installation/INSTALL_LINUX.md b/docs/installation/INSTALL_LINUX.md index 312ab60482..a871c226e6 100644 --- a/docs/installation/INSTALL_LINUX.md +++ b/docs/installation/INSTALL_LINUX.md @@ -2,6 +2,10 @@ title: Linux --- +# :fontawesome-brands-linux: Linux + +## Installation + 1. You will need to install the following prerequisites if they are not already available. Use your operating system's preferred installer. @@ -55,9 +59,11 @@ title: Linux (ldm) ~/stable-diffusion$ python3 scripts/preload_models.py ``` - Note that this step is necessary because I modified the original just-in-time - model loading scheme to allow the script to work on GPU machines that are not - internet connected. See [Preload Models](../features/OTHER.md#preload-models) + !!! note + + This step is necessary because I modified the original just-in-time + model loading scheme to allow the script to work on GPU machines that are not + internet connected. See [Preload Models](../features/OTHER.md#preload-models) 7. Now you need to install the weights for the stable diffusion model. @@ -96,15 +102,15 @@ title: Linux launch the dream script (step 8). If you forget to activate the ldm environment, the script will fail with multiple `ModuleNotFound` errors. - ### Updating to newer versions of the script +## Updating to newer versions of the script - This distribution is changing rapidly. If you used the `git clone` method - (step 5) to download the stable-diffusion directory, then to update to the - latest and greatest version, launch the Anaconda window, enter - `stable-diffusion` and type: +This distribution is changing rapidly. If you used the `git clone` method +(step 5) to download the stable-diffusion directory, then to update to the +latest and greatest version, launch the Anaconda window, enter +`stable-diffusion` and type: - ```bash - (ldm) ~/stable-diffusion$ git pull - ``` +```bash +(ldm) ~/stable-diffusion$ git pull +``` - This will bring your local copy into sync with the remote one. +This will bring your local copy into sync with the remote one. diff --git a/docs/installation/INSTALL_MAC.md b/docs/installation/INSTALL_MAC.md index 71535980f5..ce6b53a045 100644 --- a/docs/installation/INSTALL_MAC.md +++ b/docs/installation/INSTALL_MAC.md @@ -2,6 +2,8 @@ title: macOS --- +# :fontawesome-brands-apple: macOS + ## Requirements - macOS 12.3 Monterey or later @@ -9,18 +11,21 @@ title: macOS - Patience - Apple Silicon or Intel Mac -Things have moved really fast and so these instructions change often and are -often out-of-date. One of the problems is that there are so many different ways -to run this. +Things have moved really fast and so these instructions change often which makes +them outdated pretty fast. One of the problems is that there are so many +different ways to run this. We are trying to build a testing setup so that when we make changes it doesn't always break. -How to (this hasn't been 100% tested yet): +## How to -First get the weights checkpoint download started - it's big: +(this hasn't been 100% tested yet) -1. Sign up at https://huggingface.co +First get the weights checkpoint download started since it's big and will take +some time: + +1. Sign up at [huggingface.co](https://huggingface.co) 2. Go to the [Stable diffusion diffusion model page](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original) 3. Accept the terms and click Access Repository: @@ -28,114 +33,147 @@ First get the weights checkpoint download started - it's big: [sd-v1-4.ckpt (4.27 GB)](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/blob/main/sd-v1-4.ckpt) and note where you have saved it (probably the Downloads folder) - While that is downloading, open Terminal and run the following commands one - at a time. +While that is downloading, open a Terminal and run the following commands: -```bash -# install brew (and Xcode command line tools): +!!! todo "Homebrew" -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + === "no brew installation yet" -# Now there are two different routes to get the Python (miniconda) environment up and running: -# 1. Alongside pyenv -# 2. No pyenv -# -# If you don't know what we are talking about, choose 2. -# -# NOW EITHER DO -# 1. Installing alongside pyenv + ```bash title="install brew (and Xcode command line tools)" + /bin/bash -c \ + "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` - brew install pyenv-virtualenv # you might have this from before, no problem - pyenv install anaconda3-2022.05 - pyenv virtualenv anaconda3-2022.05 - eval "$(pyenv init -)" - pyenv activate anaconda3-2022.05 + === "brew is already installed" + + Only if you installed protobuf in a previous version of this tutorial, otherwise skip -# OR, -# 2. Installing standalone -# install python 3, git, cmake, protobuf: -brew install cmake protobuf rust + `#!bash brew uninstall protobuf` -# install miniconda for M1 arm64: -curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o Miniconda3-latest-MacOSX-arm64.sh -/bin/bash Miniconda3-latest-MacOSX-arm64.sh +!!! todo "Conda Installation" -# OR install miniconda for Intel: -curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -o Miniconda3-latest-MacOSX-x86_64.sh -/bin/bash Miniconda3-latest-MacOSX-x86_64.sh + Now there are two different ways to set up the Python (miniconda) environment: + 1. Standalone + 2. with pyenv + If you don't know what we are talking about, choose Standalone + === "Standalone" -# EITHER WAY, -# continue from here + ```bash + # install cmake and rust: + brew install cmake rust + ``` + === "M1 arm64" + + ```bash title="Install miniconda for M1 arm64" + curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh \ + -o Miniconda3-latest-MacOSX-arm64.sh + /bin/bash Miniconda3-latest-MacOSX-arm64.sh + ``` + + === "Intel x86_64" + + ```bash title="Install miniconda for Intel" + curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh \ + -o Miniconda3-latest-MacOSX-x86_64.sh + /bin/bash Miniconda3-latest-MacOSX-x86_64.sh + ``` + + === "with pyenv" + + ```{.bash .annotate} + brew install rust pyenv-virtualenv # (1)! + pyenv install anaconda3-2022.05 + pyenv virtualenv anaconda3-2022.05 + eval "$(pyenv init -)" + pyenv activate anaconda3-2022.05 + ``` + + 1. You might already have this installed, if that is the case just continue. + +```{.bash .annotate title="local repo setup"} # clone the repo - git clone https://github.com/lstein/stable-diffusion.git - cd stable-diffusion +git clone https://github.com/lstein/stable-diffusion.git +cd stable-diffusion -# # wait until the checkpoint file has downloaded, then proceed -# # create symlink to checkpoint - mkdir -p models/ldm/stable-diffusion-v1/ +mkdir -p models/ldm/stable-diffusion-v1/ - PATH_TO_CKPT="$HOME/Downloads" # or wherever you saved sd-v1-4.ckpt +PATH_TO_CKPT="$HOME/Downloads" # (1)! - ln -s "$PATH_TO_CKPT/sd-v1-4.ckpt" models/ldm/stable-diffusion-v1/model.ckpt +ln -s "$PATH_TO_CKPT/sd-v1-4.ckpt" \ + models/ldm/stable-diffusion-v1/model.ckpt +``` -# install packages for arm64 -PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 conda env create -f environment-mac.yaml -conda activate ldm +1. or wherever you saved sd-v1-4.ckpt -# OR install packages for x86_64 -PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-x86_64 conda env create -f environment-mac.yaml -conda activate ldm +!!! todo "create Conda Environment" + === "M1 arm64" + + ```bash + PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 \ + conda env create \ + -f environment-mac.yaml \ + && conda activate ldm + ``` + + === "Intel x86_64" + + ```bash + PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-x86_64 \ + conda env create \ + -f environment-mac.yaml \ + && conda activate ldm + ``` + +```{.bash .annotate title="preload models and run script"} # only need to do this once python scripts/preload_models.py -# run SD! -python scripts/dream.py --full_precision # half-precision requires autocast and won't work +# now you can run SD in CLI mode +python scripts/dream.py --full_precision # (1)! # or run the web interface! python scripts/dream.py --web + +# The original scripts should work as well. +python scripts/orig_scripts/txt2img.py \ + --prompt "a photograph of an astronaut riding a horse" \ + --plms ``` -The original scripts should work as well. +1. half-precision requires autocast which is unfortunatelly incompatible -```bash -python scripts/orig_scripts/txt2img.py --prompt "a photograph of an astronaut riding a horse" --plms -``` +!!! note -Note, + `#!bash export PIP_EXISTS_ACTION=w` is a precaution to fix a problem where -```bash -export PIP_EXISTS_ACTION=w -``` + ```bash + conda env create \ + -f environment-mac.yaml + ``` -is a precaution to fix - -```bash -conda env create -f environment-mac.yaml -``` - -never finishing in some situations. So it isn't required but wont hurt. - -After you follow all the instructions and run dream.py you might get several -errors. Here's the errors I've seen and found solutions for. + did never finish in some situations. So it isn't required but wont hurt. --- +## Common problems + +After you followed all the instructions and try to run dream.py, you might +get several errors. Here's the errors I've seen and found solutions for. + ### Is it slow? -Be sure to specify 1 sample and 1 iteration. - -```bash +```bash title="Be sure to specify 1 sample and 1 iteration." python ./scripts/orig_scripts/txt2img.py \ - --prompt "ocean" \ - --ddim_steps 5 \ - --n_samples 1 \ - --n_iter 1 + --prompt "ocean" \ + --ddim_steps 5 \ + --n_samples 1 \ + --n_iter 1 ``` --- @@ -153,55 +191,75 @@ solution please One debugging step is to update to the latest version of PyTorch nightly. ```bash -conda install pytorch torchvision torchaudio -c pytorch-nightly +conda install \ + pytorch \ + torchvision \ + -c pytorch-nightly \ + -n ldm ``` If it takes forever to run ```bash -conda env create -f environment-mac.yaml +conda env create \ + -f environment-mac.yaml ``` -you could try to run `git clean -f` followed by: +you could try to run: -`conda clean --yes --all` +```bash +git clean -f +conda clean \ + --yes \ + --all +``` Or you could try to completley reset Anaconda: ```bash -conda update --force-reinstall -y -n base -c defaults conda +conda update \ + --force-reinstall \ + -y \ + -n base \ + -c defaults conda ``` --- ### "No module named cv2", torch, 'ldm', 'transformers', 'taming', etc -There are several causes of these errors. +There are several causes of these errors: -- First, did you remember to `conda activate ldm`? If your terminal prompt - begins with "(ldm)" then you activated it. If it begins with "(base)" or - something else you haven't. +1. Did you remember to `conda activate ldm`? If your terminal prompt begins with + "(ldm)" then you activated it. If it begins with "(base)" or something else + you haven't. -- Second, you might've run `./scripts/preload_models.py` or `./scripts/dream.py` - instead of `python ./scripts/preload_models.py` or - `python ./scripts/dream.py`. The cause of this error is long so it's below. +2. You might've run `./scripts/preload_models.py` or `./scripts/dream.py` + instead of `python ./scripts/preload_models.py` or + `python ./scripts/dream.py`. The cause of this error is long so it's below. -- Third, if it says you're missing taming you need to rebuild your virtual - environment. + -````bash -conda deactivate +3. if it says you're missing taming you need to rebuild your virtual + environment. -conda env remove -n ldm -PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 conda env create -f environment-mac.yaml -``` + ```bash + conda deactivate + conda env remove -n ldm + PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 \ + conda env create \ + -f environment-mac.yaml + ``` -Fourth, If you have activated the ldm virtual environment and tried rebuilding -it, maybe the problem could be that I have something installed that you don't -and you'll just need to manually install it. Make sure you activate the virtual -environment so it installs there instead of globally. +4. If you have activated the ldm virtual environment and tried rebuilding it, + maybe the problem could be that I have something installed that you don't and + you'll just need to manually install it. Make sure you activate the virtual + environment so it installs there instead of globally. -`conda activate ldm pip install _name_` + ```bash + conda activate ldm + pip install + ``` You might also need to install Rust (I mention this again below). @@ -261,21 +319,20 @@ output of `python3 -V` and `python -V`. /Users/name/miniforge3/envs/ldm/bin/python ``` -The above is what you'll see if you have miniforge and you've correctly -activated the ldm environment, and you used option 2 in the setup instructions -above ("no pyenv"). +The above is what you'll see if you have miniforge and correctly activated the +ldm environment, while usingd the standalone setup instructions above. + +If you otherwise installed via pyenv, you will get this result: ```bash (anaconda3-2022.05) % which python /Users/name/.pyenv/shims/python ``` -... and the above is what you'll see if you used option 1 ("Alongside pyenv"). - It's all a mess and you should know [how to modify the path environment variable](https://support.apple.com/guide/terminal/use-environment-variables-apd382cc5fa-4f58-4449-b20a-41c53c006f8f/mac) -if you want to fix it. Here's a brief hint of all the ways you can modify it -(don't really have the time to explain it all here). +if you want to fix it. Here's a brief hint of the most common ways you can +modify it (don't really have the time to explain it all here). - ~/.zshrc - ~/.bash_profile @@ -283,16 +340,21 @@ if you want to fix it. Here's a brief hint of all the ways you can modify it - /etc/paths.d - /etc/path -Which one you use will depend on what you have installed except putting a file -in /etc/paths.d is what I prefer to do. +Which one you use will depend on what you have installed, except putting a file +in /etc/paths.d - which also is the way I prefer to do. Finally, to answer the question posed by this section's title, it may help to list all of the `python` / `python3` things found in `$PATH` instead of just the -one that will be executed by default. To do that, add the `-a` switch to -`which`: +first hit. To do so, add the `-a` switch to `which`: - % which -a python3 - ... +```bash +% which -a python3 +... +``` + +This will show a list of all binaries which are actually available in your PATH. + +--- ### Debugging? @@ -300,37 +362,56 @@ Tired of waiting for your renders to finish before you can see if it works? Reduce the steps! The image quality will be horrible but at least you'll get quick feedback. - python ./scripts/txt2img.py --prompt "ocean" --ddim_steps 5 --n_samples 1 --n_iter 1 +```bash +python ./scripts/txt2img.py \ + --prompt "ocean" \ + --ddim_steps 5 \ + --n_samples 1 \ + --n_iter 1 +``` -### OSError: Can't load tokenizer for 'openai/clip-vit-large-patch14'... +--- - python scripts/preload_models.py +### OSError: Can't load tokenizer for 'openai/clip-vit-large-patch14' + +```bash +python scripts/preload_models.py +``` + +--- ### "The operator [name] is not current implemented for the MPS device." (sic) -Example error. +!!! example "example error" -``` + ```bash + ... NotImplementedError: The operator 'aten::_index_put_impl_' is not current + implemented for the MPS device. If you want this op to be added in priority + during the prototype phase of this feature, please comment on + https://github.com/pytorch/pytorch/issues/77764. + As a temporary fix, you can set the environment variable + `PYTORCH_ENABLE_MPS_FALLBACK=1` to use the CPU as a fallback for this op. + WARNING: this will be slower than running natively on MPS. + ``` -... NotImplementedError: The operator 'aten::_index_put_impl_' is not current -implemented for the MPS device. If you want this op to be added in priority -during the prototype phase of this feature, please comment on -[https://github.com/pytorch/pytorch/issues/77764](https://github.com/pytorch/pytorch/issues/77764). -As a temporary fix, you can set the environment variable -`PYTORCH_ENABLE_MPS_FALLBACK=1` to use the CPU as a fallback for this op. -WARNING: this will be slower than running natively on MPS. - -``` - -The lstein branch includes this fix in +This fork already includes a fix for this in [environment-mac.yaml](https://github.com/lstein/stable-diffusion/blob/main/environment-mac.yaml). +--- + ### "Could not build wheels for tokenizers" I have not seen this error because I had Rust installed on my computer before I started playing with Stable Diffusion. The fix is to install Rust. - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +```bash +curl \ + --proto '=https' \ + --tlsv1.2 \ + -sSf https://sh.rustup.rs | sh +``` + +--- ### How come `--seed` doesn't work? @@ -347,7 +428,9 @@ still working on it. ### libiomp5.dylib error? - OMP: Error #15: Initializing libiomp5.dylib, but found libomp.dylib already initialized. +```bash +OMP: Error #15: Initializing libiomp5.dylib, but found libomp.dylib already initialized. +``` You are likely using an Intel package by mistake. Be sure to run conda with the environment variable `CONDA_SUBDIR=osx-arm64`, like so: @@ -363,6 +446,8 @@ is a metapackage designed to prevent this, by making it impossible to install Do _not_ use `os.environ['KMP_DUPLICATE_LIB_OK']='True'` or equivalents as this masks the underlying issue of using Intel packages. +--- + ### Not enough memory This seems to be a common problem and is probably the underlying problem for a @@ -374,6 +459,8 @@ how that would affect the quality of the images though. See [this issue](https://github.com/CompVis/stable-diffusion/issues/71). +--- + ### "Error: product of dimension sizes > 2\*\*31'" This error happens with img2img, which I haven't played with too much yet. But I @@ -388,6 +475,8 @@ BTW, 2\*\*31-1 = is also 32-bit signed [LONG_MAX](https://en.wikipedia.org/wiki/C_data_types) in C. +--- + ### I just got Rickrolled! Do I have a virus? You don't have a virus. It's part of the project. Here's @@ -400,6 +489,8 @@ call this "computer vision", sheesh). Actually, this could be happening because there's not enough RAM. You could try the `model.half()` suggestion or specify smaller output images. +--- + ### My images come out black We might have this fixed, we are still testing. @@ -428,7 +519,10 @@ This is a 32-bit vs 16-bit problem. ### The processor must support the Intel bla bla bla What? Intel? On an Apple Silicon? -`bash Intel MKL FATAL ERROR: This system does not meet the minimum requirements for use of the Intel(R) Math Kernel Library. The processor must support the Intel(R) Supplemental Streaming SIMD Extensions 3 (Intel(R) SSSE3) instructions. The processor must support the Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) instructions. The processor must support the Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions. ` + +```bash +Intel MKL FATAL ERROR: This system does not meet the minimum requirements for use of the Intel(R) Math Kernel Library. The processor must support the Intel(R) Supplemental Streaming SIMD Extensions 3 (Intel(R) SSSE3) instructions. The processor must support the Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) instructions. The processor must support the Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions. +``` This is due to the Intel `mkl` package getting picked up when you try to install something that depends on it-- Rosetta can translate some Intel instructions but @@ -453,5 +547,3 @@ Abort trap: 6 warnings.warn('resource_tracker: There appear to be %d ' ``` -Macs do not support `autocast/mixed-precision`, so you need to supply -`--full_precision` to use float32 everywhere. diff --git a/docs/installation/INSTALL_WINDOWS.md b/docs/installation/INSTALL_WINDOWS.md index 8119449717..d0f6355bb2 100644 --- a/docs/installation/INSTALL_WINDOWS.md +++ b/docs/installation/INSTALL_WINDOWS.md @@ -2,6 +2,8 @@ title: Windows --- +# :fontawesome-brands-windows: Windows + ## **Notebook install (semi-automated)** We have a @@ -28,17 +30,16 @@ in the wiki ### **Conda** -1. Install Anaconda3 (miniconda3 version) from here: - https://docs.anaconda.com/anaconda/install/windows/ +1. Install Anaconda3 (miniconda3 version) from [here](https://docs.anaconda.com/anaconda/install/windows/) -2. Install Git from here: https://git-scm.com/download/win +2. Install Git from [here](https://git-scm.com/download/win) 3. Launch Anaconda from the Windows Start menu. This will bring up a command window. Type all the remaining commands in this window. 4. Run the command: - ```bash + ```batch git clone https://github.com/lstein/stable-diffusion.git ``` @@ -48,15 +49,15 @@ in the wiki 5. Enter the newly-created stable-diffusion folder. From this step forward make sure that you are working in the stable-diffusion directory! - ```bash + ```batch cd stable-diffusion ``` 6. Run the following two commands: - ```bash - conda env create -f environment.yaml (step 6a) - conda activate ldm (step 6b) + ```batch + conda env create -f environment.yaml + conda activate ldm ``` This will install all python requirements and activate the "ldm" environment @@ -64,7 +65,7 @@ in the wiki 7. Run the command: - ```bash + ```batch python scripts\preload_models.py ``` @@ -77,9 +78,9 @@ in the wiki 8. Now you need to install the weights for the big stable diffusion model. - For running with the released weights, you will first need to set up an - acount with Hugging Face (https://huggingface.co). + acount with [Hugging Face](https://huggingface.co). - Use your credentials to log in, and then point your browser at - https://huggingface.co/CompVis/stable-diffusion-v-1-4-original. + [https://huggingface.co/CompVis/stable-diffusion-v-1-4-original](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original). - You may be asked to sign a license agreement at this point. - Click on "Files and versions" near the top of the page, and then click on the file named `sd-v1-4.ckpt`. You'll be taken to a page that prompts you @@ -90,7 +91,7 @@ in the wiki Now run the following commands from **within the stable-diffusion directory** to copy the weights file to the right place: - ```bash + ```batch mkdir -p models\ldm\stable-diffusion-v1 copy C:\path\to\sd-v1-4.ckpt models\ldm\stable-diffusion-v1\model.ckpt ``` @@ -102,7 +103,7 @@ in the wiki 9. Start generating images! - ```bash + ```batch # for the pre-release weights python scripts\dream.py -l @@ -122,14 +123,14 @@ in the wiki --- -### Updating to newer versions of the script +## Updating to newer versions of the script This distribution is changing rapidly. If you used the `git clone` method (step 5) to download the stable-diffusion directory, then to update to the latest and greatest version, launch the Anaconda window, enter `stable-diffusion`, and type: -```bash +```batch git pull conda env update -f environment.yaml ``` diff --git a/docs/other/CONTRIBUTORS.md b/docs/other/CONTRIBUTORS.md index 7eba44dbad..dcea33492a 100644 --- a/docs/other/CONTRIBUTORS.md +++ b/docs/other/CONTRIBUTORS.md @@ -2,6 +2,8 @@ title: Contributors --- +# :octicons-person-24: Contributors + The list of all the amazing people who have contributed to the various features that you get to experience in this fork. diff --git a/environment-mac.yaml b/environment-mac.yaml index 8e1007d4cf..95f38438e2 100644 --- a/environment-mac.yaml +++ b/environment-mac.yaml @@ -32,6 +32,7 @@ dependencies: - omegaconf==2.1.1 - onnx==1.12.0 - onnxruntime==1.12.1 + - protobuf==3.20.1 - pudb==2022.1 - pytorch-lightning==1.6.5 - scipy==1.9.1 @@ -48,6 +49,7 @@ dependencies: - opencv-python==4.6.0 - protobuf==3.20.1 - realesrgan==0.2.5.0 + - send2trash==1.8.0 - test-tube==0.7.5 - transformers==4.21.2 - torch-fidelity==0.3.0 diff --git a/environment.yaml b/environment.yaml index b465d92585..eaf4d0e02a 100644 --- a/environment.yaml +++ b/environment.yaml @@ -20,6 +20,7 @@ dependencies: - realesrgan==0.2.5.0 - test-tube>=0.7.5 - streamlit==1.12.0 + - send2trash==1.8.0 - pillow==9.2.0 - einops==0.3.0 - torch-fidelity==0.3.0 diff --git a/frontend/README.md b/frontend/README.md index 6928e27b49..94934b2bce 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,85 +1,37 @@ # Stable Diffusion Web UI -Demo at https://peaceful-otter-7a427f.netlify.app/ (not connected to back end) +## Run -much of this readme is just notes for myself during dev work +- `python backend/server.py` serves both frontend and backend at http://localhost:9090 -numpy rand: 0 to 4294967295 +## Evironment -## Test and Build +Install [node](https://nodejs.org/en/download/) (includes npm) and optionally +[yarn](https://yarnpkg.com/getting-started/install). -from `frontend/`: +From `frontend/` run `npm install` / `yarn install` to install the frontend packages. -- `yarn dev` runs `tsc-watch`, which runs `vite build` on successful `tsc` transpilation +## Dev -from `.`: +1. From `frontend/`, run `npm dev` / `yarn dev` to start the dev server. +2. Note the address it starts up on (probably `http://localhost:5173/`). +3. Edit `backend/server.py`'s `additional_allowed_origins` to include this address, e.g. + `additional_allowed_origins = ['http://localhost:5173']`. +4. Leaving the dev server running, open a new terminal and go to the project root. +5. Run `python backend/server.py`. +6. Navigate to the dev server address e.g. `http://localhost:5173/`. -- `python backend/server.py` serves both frontend and backend at http://localhost:9090 +To build for dev: `npm build-dev` / `yarn build-dev` -## API - -`backend/server.py` serves the UI and provides a [socket.io](https://github.com/socketio/socket.io) API via [flask-socketio](https://github.com/miguelgrinberg/flask-socketio). - -### Server Listeners - -The server listens for these socket.io events: - -`cancel` - -- Cancels in-progress image generation -- Returns ack only - -`generateImage` - -- Accepts object of image parameters -- Generates an image -- Returns ack only (image generation function sends progress and result via separate events) - -`deleteImage` - -- Accepts file path to image -- Deletes image -- Returns ack only - -`deleteAllImages` WIP - -- Deletes all images in `outputs/` -- Returns ack only - -`requestAllImages` - -- Returns array of all images in `outputs/` - -`requestCapabilities` WIP - -- Returns capabilities of server (torch device, GFPGAN and ESRGAN availability, ???) - -`sendImage` WIP - -- Accepts a File and attributes -- Saves image -- Used to save init images which are not generated images - -### Server Emitters - -`progress` - -- Emitted during each step in generation -- Sends a number from 0 to 1 representing percentage of steps completed - -`result` WIP - -- Emitted when an image generation has completed -- Sends a object: - -``` -{ - url: relative_file_path, - metadata: image_metadata_object -} -``` +To build for production: `npm build` / `yarn build` ## TODO -- Search repo for "TODO" -- My one gripe with Chakra: no way to disable all animations right now and drop the dependence on `framer-motion`. I would prefer to save the ~30kb on bundle and have zero animations. This is on the Chakra roadmap. See https://github.com/chakra-ui/chakra-ui/pull/6368 for last discussion on this. Need to check in on this issue periodically. +- Search repo for "TODO" +- My one gripe with Chakra: no way to disable all animations right now and drop the dependence on + `framer-motion`. I would prefer to save the ~30kb on bundle and have zero animations. This is on + the Chakra roadmap. See https://github.com/chakra-ui/chakra-ui/pull/6368 for last discussion on + this. Need to check in on this issue periodically. +- Mobile friendly layout +- Proper image gallery/viewer/manager +- Help tooltips and such diff --git a/frontend/dist/assets/index.727a397b.js b/frontend/dist/assets/index.727a397b.js new file mode 100644 index 0000000000..cc3b6e6fca --- /dev/null +++ b/frontend/dist/assets/index.727a397b.js @@ -0,0 +1,694 @@ +function p$(e,t){for(var r=0;ri[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))i(o);new MutationObserver(o=>{for(const c of o)if(c.type==="childList")for(const u of c.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&i(u)}).observe(document,{childList:!0,subtree:!0});function r(o){const c={};return o.integrity&&(c.integrity=o.integrity),o.referrerpolicy&&(c.referrerPolicy=o.referrerpolicy),o.crossorigin==="use-credentials"?c.credentials="include":o.crossorigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function i(o){if(o.ep)return;o.ep=!0;const c=r(o);fetch(o.href,c)}})();var hc=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function h$(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var D={exports:{}},LR={exports:{}};/** + * @license React + * react.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(e,t){(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var r="18.2.0",i=Symbol.for("react.element"),o=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),u=Symbol.for("react.strict_mode"),h=Symbol.for("react.profiler"),m=Symbol.for("react.provider"),v=Symbol.for("react.context"),S=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),w=Symbol.for("react.suspense_list"),_=Symbol.for("react.memo"),R=Symbol.for("react.lazy"),k=Symbol.for("react.offscreen"),P=Symbol.iterator,U="@@iterator";function L(b){if(b===null||typeof b!="object")return null;var A=P&&b[P]||b[U];return typeof A=="function"?A:null}var F={current:null},B={transition:null},$={current:null,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1},K={current:null},Z={},fe=null;function me(b){fe=b}Z.setExtraStackFrame=function(b){fe=b},Z.getCurrentStack=null,Z.getStackAddendum=function(){var b="";fe&&(b+=fe);var A=Z.getCurrentStack;return A&&(b+=A()||""),b};var se=!1,Se=!1,Ke=!1,ae=!1,ce=!1,ge={ReactCurrentDispatcher:F,ReactCurrentBatchConfig:B,ReactCurrentOwner:K};ge.ReactDebugCurrentFrame=Z,ge.ReactCurrentActQueue=$;function _e(b){{for(var A=arguments.length,V=new Array(A>1?A-1:0),G=1;G1?A-1:0),G=1;G1){for(var At=Array(yt),ht=0;ht1){for(var zt=Array(ht),_t=0;_t is not supported and will be removed in a future major release. Did you mean to render instead?")),A.Provider},set:function(Ce){A.Provider=Ce}},_currentValue:{get:function(){return A._currentValue},set:function(Ce){A._currentValue=Ce}},_currentValue2:{get:function(){return A._currentValue2},set:function(Ce){A._currentValue2=Ce}},_threadCount:{get:function(){return A._threadCount},set:function(Ce){A._threadCount=Ce}},Consumer:{get:function(){return V||(V=!0,oe("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?")),A.Consumer}},displayName:{get:function(){return A.displayName},set:function(Ce){te||(_e("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.",Ce),te=!0)}}}),A.Consumer=Fe}return A._currentRenderer=null,A._currentRenderer2=null,A}var pr=-1,Ma=0,Ii=1,Pa=2;function q(b){if(b._status===pr){var A=b._result,V=A();if(V.then(function(Fe){if(b._status===Ma||b._status===pr){var Ce=b;Ce._status=Ii,Ce._result=Fe}},function(Fe){if(b._status===Ma||b._status===pr){var Ce=b;Ce._status=Pa,Ce._result=Fe}}),b._status===pr){var G=b;G._status=Ma,G._result=V}}if(b._status===Ii){var te=b._result;return te===void 0&&oe(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent')) + +Did you accidentally put curly braces around the import?`,te),"default"in te||oe(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent'))`,te),te.default}else throw b._result}function Ue(b){var A={_status:pr,_result:b},V={$$typeof:R,_payload:A,_init:q};{var G,te;Object.defineProperties(V,{defaultProps:{configurable:!0,get:function(){return G},set:function(Fe){oe("React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),G=Fe,Object.defineProperty(V,"defaultProps",{enumerable:!0})}},propTypes:{configurable:!0,get:function(){return te},set:function(Fe){oe("React.lazy(...): It is not supported to assign `propTypes` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),te=Fe,Object.defineProperty(V,"propTypes",{enumerable:!0})}}})}return V}function qe(b){b!=null&&b.$$typeof===_?oe("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof b!="function"?oe("forwardRef requires a render function but was given %s.",b===null?"null":typeof b):b.length!==0&&b.length!==2&&oe("forwardRef render functions accept exactly two parameters: props and ref. %s",b.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),b!=null&&(b.defaultProps!=null||b.propTypes!=null)&&oe("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?");var A={$$typeof:S,render:b};{var V;Object.defineProperty(A,"displayName",{enumerable:!1,configurable:!0,get:function(){return V},set:function(G){V=G,!b.name&&!b.displayName&&(b.displayName=G)}})}return A}var St;St=Symbol.for("react.module.reference");function an(b){return!!(typeof b=="string"||typeof b=="function"||b===c||b===h||ce||b===u||b===x||b===w||ae||b===k||se||Se||Ke||typeof b=="object"&&b!==null&&(b.$$typeof===R||b.$$typeof===_||b.$$typeof===m||b.$$typeof===v||b.$$typeof===S||b.$$typeof===St||b.getModuleId!==void 0))}function Sn(b,A){an(b)||oe("memo: The first argument must be a component. Instead received: %s",b===null?"null":typeof b);var V={$$typeof:_,type:b,compare:A===void 0?null:A};{var G;Object.defineProperty(V,"displayName",{enumerable:!1,configurable:!0,get:function(){return G},set:function(te){G=te,!b.name&&!b.displayName&&(b.displayName=te)}})}return V}function tt(){var b=F.current;return b===null&&oe(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: +1. You might have mismatching versions of React and the renderer (such as React DOM) +2. You might be breaking the Rules of Hooks +3. You might have more than one copy of React in the same app +See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.`),b}function Ht(b){var A=tt();if(b._context!==void 0){var V=b._context;V.Consumer===b?oe("Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be removed in a future major release. Did you mean to call useContext(Context) instead?"):V.Provider===b&&oe("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?")}return A.useContext(b)}function jn(b){var A=tt();return A.useState(b)}function zn(b,A,V){var G=tt();return G.useReducer(b,A,V)}function sn(b){var A=tt();return A.useRef(b)}function zr(b,A){var V=tt();return V.useEffect(b,A)}function hi(b,A){var V=tt();return V.useInsertionEffect(b,A)}function Do(b,A){var V=tt();return V.useLayoutEffect(b,A)}function va(b,A){var V=tt();return V.useCallback(b,A)}function io(b,A){var V=tt();return V.useMemo(b,A)}function wu(b,A,V){var G=tt();return G.useImperativeHandle(b,A,V)}function mi(b,A){{var V=tt();return V.useDebugValue(b,A)}}function $s(){var b=tt();return b.useTransition()}function Fi(b){var A=tt();return A.useDeferredValue(b)}function Jt(){var b=tt();return b.useId()}function zi(b,A,V){var G=tt();return G.useSyncExternalStore(b,A,V)}var ga=0,Mo,os,Po,ss,ls,Lo,Io;function us(){}us.__reactDisabledLog=!0;function Vs(){{if(ga===0){Mo=console.log,os=console.info,Po=console.warn,ss=console.error,ls=console.group,Lo=console.groupCollapsed,Io=console.groupEnd;var b={configurable:!0,enumerable:!0,value:us,writable:!0};Object.defineProperties(console,{info:b,log:b,warn:b,error:b,group:b,groupCollapsed:b,groupEnd:b})}ga++}}function Hs(){{if(ga--,ga===0){var b={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Le({},b,{value:Mo}),info:Le({},b,{value:os}),warn:Le({},b,{value:Po}),error:Le({},b,{value:ss}),group:Le({},b,{value:ls}),groupCollapsed:Le({},b,{value:Lo}),groupEnd:Le({},b,{value:Io})})}ga<0&&oe("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var vi=ge.ReactCurrentDispatcher,Pr;function La(b,A,V){{if(Pr===void 0)try{throw Error()}catch(te){var G=te.stack.trim().match(/\n( *(at )?)/);Pr=G&&G[1]||""}return` +`+Pr+b}}var ya=!1,Ia;{var cs=typeof WeakMap=="function"?WeakMap:Map;Ia=new cs}function Fo(b,A){if(!b||ya)return"";{var V=Ia.get(b);if(V!==void 0)return V}var G;ya=!0;var te=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var Fe;Fe=vi.current,vi.current=null,Vs();try{if(A){var Ce=function(){throw Error()};if(Object.defineProperty(Ce.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(Ce,[])}catch(Pt){G=Pt}Reflect.construct(b,[],Ce)}else{try{Ce.call()}catch(Pt){G=Pt}b.call(Ce.prototype)}}else{try{throw Error()}catch(Pt){G=Pt}b()}}catch(Pt){if(Pt&&G&&typeof Pt.stack=="string"){for(var $e=Pt.stack.split(` +`),it=G.stack.split(` +`),yt=$e.length-1,At=it.length-1;yt>=1&&At>=0&&$e[yt]!==it[At];)At--;for(;yt>=1&&At>=0;yt--,At--)if($e[yt]!==it[At]){if(yt!==1||At!==1)do if(yt--,At--,At<0||$e[yt]!==it[At]){var ht=` +`+$e[yt].replace(" at new "," at ");return b.displayName&&ht.includes("")&&(ht=ht.replace("",b.displayName)),typeof b=="function"&&Ia.set(b,ht),ht}while(yt>=1&&At>=0);break}}}finally{ya=!1,vi.current=Fe,Hs(),Error.prepareStackTrace=te}var zt=b?b.displayName||b.name:"",_t=zt?La(zt):"";return typeof b=="function"&&Ia.set(b,_t),_t}function fs(b,A,V){return Fo(b,!1)}function Dl(b){var A=b.prototype;return!!(A&&A.isReactComponent)}function ba(b,A,V){if(b==null)return"";if(typeof b=="function")return Fo(b,Dl(b));if(typeof b=="string")return La(b);switch(b){case x:return La("Suspense");case w:return La("SuspenseList")}if(typeof b=="object")switch(b.$$typeof){case S:return fs(b.render);case _:return ba(b.type,A,V);case R:{var G=b,te=G._payload,Fe=G._init;try{return ba(Fe(te),A,V)}catch{}}}return""}var zo={},Fa=ge.ReactDebugCurrentFrame;function gi(b){if(b){var A=b._owner,V=ba(b.type,b._source,A?A.type:null);Fa.setExtraStackFrame(V)}else Fa.setExtraStackFrame(null)}function Ws(b,A,V,G,te){{var Fe=Function.call.bind(hn);for(var Ce in b)if(Fe(b,Ce)){var $e=void 0;try{if(typeof b[Ce]!="function"){var it=Error((G||"React class")+": "+V+" type `"+Ce+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof b[Ce]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw it.name="Invariant Violation",it}$e=b[Ce](A,Ce,G,V,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(yt){$e=yt}$e&&!($e instanceof Error)&&(gi(te),oe("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",G||"React class",V,Ce,typeof $e),gi(null)),$e instanceof Error&&!($e.message in zo)&&(zo[$e.message]=!0,gi(te),oe("Failed %s type: %s",V,$e.message),gi(null))}}}function ln(b){if(b){var A=b._owner,V=ba(b.type,b._source,A?A.type:null);me(V)}else me(null)}var yi;yi=!1;function Bo(){if(K.current){var b=jt(K.current.type);if(b)return` + +Check the render method of \``+b+"`."}return""}function Ft(b){if(b!==void 0){var A=b.fileName.replace(/^.*[\\\/]/,""),V=b.lineNumber;return` + +Check your code at `+A+":"+V+"."}return""}function Gs(b){return b!=null?Ft(b.__source):""}var xr={};function Bi(b){var A=Bo();if(!A){var V=typeof b=="string"?b:b.displayName||b.name;V&&(A=` + +Check the top-level render call using <`+V+">.")}return A}function Ha(b,A){if(!(!b._store||b._store.validated||b.key!=null)){b._store.validated=!0;var V=Bi(A);if(!xr[V]){xr[V]=!0;var G="";b&&b._owner&&b._owner!==K.current&&(G=" It was passed a child from "+jt(b._owner.type)+"."),ln(b),oe('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',V,G),ln(null)}}}function oo(b,A){if(typeof b=="object"){if(Kt(b))for(var V=0;V",te=" Did you accidentally export a JSX literal instead of a component?"):Ce=typeof b,oe("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",Ce,te)}var $e=rt.apply(this,arguments);if($e==null)return $e;if(G)for(var it=2;it10&&_e("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."),G._updatedFibers.clear()}}}var so=!1,bi=null;function Ys(b){if(bi===null)try{var A=("require"+Math.random()).slice(0,7),V=e&&e[A];bi=V.call(e,"timers").setImmediate}catch{bi=function(te){so===!1&&(so=!0,typeof MessageChannel>"u"&&oe("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var Fe=new MessageChannel;Fe.port1.onmessage=te,Fe.port2.postMessage(void 0)}}return bi(b)}var vn=0,In=!1;function Ml(b){{var A=vn;vn++,$.current===null&&($.current=[]);var V=$.isBatchingLegacy,G;try{if($.isBatchingLegacy=!0,G=b(),!V&&$.didScheduleLegacyUpdate){var te=$.current;te!==null&&($.didScheduleLegacyUpdate=!1,de(te))}}catch(zt){throw za(A),zt}finally{$.isBatchingLegacy=V}if(G!==null&&typeof G=="object"&&typeof G.then=="function"){var Fe=G,Ce=!1,$e={then:function(zt,_t){Ce=!0,Fe.then(function(Pt){za(A),vn===0?W(Pt,zt,_t):zt(Pt)},function(Pt){za(A),_t(Pt)})}};return!In&&typeof Promise<"u"&&Promise.resolve().then(function(){}).then(function(){Ce||(In=!0,oe("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),$e}else{var it=G;if(za(A),vn===0){var yt=$.current;yt!==null&&(de(yt),$.current=null);var At={then:function(zt,_t){$.current===null?($.current=[],W(it,zt,_t)):zt(it)}};return At}else{var ht={then:function(zt,_t){zt(it)}};return ht}}}}function za(b){b!==vn-1&&oe("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),vn=b}function W(b,A,V){{var G=$.current;if(G!==null)try{de(G),Ys(function(){G.length===0?($.current=null,A(b)):W(b,A,V)})}catch(te){V(te)}else A(b)}}var Q=!1;function de(b){if(!Q){Q=!0;var A=0;try{for(;A0;){var Qt=mn-1>>>1,Nn=je[Qt];if(v(Nn,rt)>0)je[Qt]=rt,je[mn]=Nn,mn=Qt;else return}}function m(je,rt,wt){for(var mn=wt,Qt=je.length,Nn=Qt>>>1;mnwt&&(!je||lr()));){var mn=ae.callback;if(typeof mn=="function"){ae.callback=null,ce=ae.priorityLevel;var Qt=ae.expirationTime<=wt,Nn=mn(Qt);wt=e.unstable_now(),typeof Nn=="function"?ae.callback=Nn:ae===c(se)&&u(se),we(wt)}else u(se);ae=c(se)}if(ae!==null)return!0;var Ln=c(Se);return Ln!==null&&Rt(Le,Ln.startTime-wt),!1}function st(je,rt){switch(je){case S:case x:case w:case _:case R:break;default:je=w}var wt=ce;ce=je;try{return rt()}finally{ce=wt}}function vt(je){var rt;switch(ce){case S:case x:case w:rt=w;break;default:rt=ce;break}var wt=ce;ce=rt;try{return je()}finally{ce=wt}}function qt(je){var rt=ce;return function(){var wt=ce;ce=rt;try{return je.apply(this,arguments)}finally{ce=wt}}}function Qe(je,rt,wt){var mn=e.unstable_now(),Qt;if(typeof wt=="object"&&wt!==null){var Nn=wt.delay;typeof Nn=="number"&&Nn>0?Qt=mn+Nn:Qt=mn}else Qt=mn;var Ln;switch(je){case S:Ln=$;break;case x:Ln=K;break;case R:Ln=me;break;case _:Ln=fe;break;case w:default:Ln=Z;break}var kr=Qt+Ln,kn={id:Ke++,callback:rt,priorityLevel:je,startTime:Qt,expirationTime:kr,sortIndex:-1};return Qt>mn?(kn.sortIndex=Qt,o(Se,kn),c(se)===null&&kn===c(Se)&&(oe?Oe():oe=!0,Rt(Le,Qt-mn))):(kn.sortIndex=kr,o(se,kn),!_e&&!ge&&(_e=!0,tn(Ie))),kn}function gt(){}function Tt(){!_e&&!ge&&(_e=!0,tn(Ie))}function Ut(){return c(se)}function We(je){je.callback=null}function Kt(){return ce}var be=!1,It=null,Xt=-1,Ct=i,sr=-1;function lr(){var je=e.unstable_now()-sr;return!(je125){console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported");return}je>0?Ct=Math.floor(1e3/je):Ct=i}var Pn=function(){if(It!==null){var je=e.unstable_now();sr=je;var rt=!0,wt=!0;try{wt=It(rt,je)}finally{wt?gn():(be=!1,It=null)}}else be=!1},gn;if(typeof pe=="function")gn=function(){pe(Pn)};else if(typeof MessageChannel<"u"){var Ve=new MessageChannel,Xe=Ve.port2;Ve.port1.onmessage=Pn,gn=function(){Xe.postMessage(null)}}else gn=function(){xe(Pn,0)};function tn(je){It=je,be||(be=!0,gn())}function Rt(je,rt){Xt=xe(function(){je(e.unstable_now())},rt)}function Oe(){Te(Xt),Xt=-1}var Vt=jt,_n=null;e.unstable_IdlePriority=R,e.unstable_ImmediatePriority=S,e.unstable_LowPriority=_,e.unstable_NormalPriority=w,e.unstable_Profiling=_n,e.unstable_UserBlockingPriority=x,e.unstable_cancelCallback=We,e.unstable_continueExecution=Tt,e.unstable_forceFrameRate=hn,e.unstable_getCurrentPriorityLevel=Kt,e.unstable_getFirstCallbackNode=Ut,e.unstable_next=vt,e.unstable_pauseExecution=gt,e.unstable_requestPaint=Vt,e.unstable_runWithPriority=st,e.unstable_scheduleCallback=Qe,e.unstable_shouldYield=lr,e.unstable_wrapCallback=qt,typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error)})()})(AL);(function(e){e.exports=AL})(RL);/** + * @license React + * react-dom.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var e=D.exports,t=RL.exports,r=e.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,i=!1;function o(n){i=n}function c(n){if(!i){for(var a=arguments.length,s=new Array(a>1?a-1:0),f=1;f1?a-1:0),f=1;f2&&(n[0]==="o"||n[0]==="O")&&(n[1]==="n"||n[1]==="N")}function kr(n,a,s,f){if(s!==null&&s.type===Ve)return!1;switch(typeof a){case"function":case"symbol":return!0;case"boolean":{if(f)return!1;if(s!==null)return!s.acceptsBooleans;var p=n.toLowerCase().slice(0,5);return p!=="data-"&&p!=="aria-"}default:return!1}}function kn(n,a,s,f){if(a===null||typeof a>"u"||kr(n,a,s,f))return!0;if(f)return!1;if(s!==null)switch(s.type){case Rt:return!a;case Oe:return a===!1;case Vt:return isNaN(a);case _n:return isNaN(a)||a<1}return!1}function ha(n){return En.hasOwnProperty(n)?En[n]:null}function Un(n,a,s,f,p,y,C){this.acceptsBooleans=a===tn||a===Rt||a===Oe,this.attributeName=f,this.attributeNamespace=p,this.mustUseProperty=s,this.propertyName=n,this.type=a,this.sanitizeURL=y,this.removeEmptyString=C}var En={},ma=["children","dangerouslySetInnerHTML","defaultValue","defaultChecked","innerHTML","suppressContentEditableWarning","suppressHydrationWarning","style"];ma.forEach(function(n){En[n]=new Un(n,Ve,!1,n,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(n){var a=n[0],s=n[1];En[a]=new Un(a,Xe,!1,s,null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(n){En[n]=new Un(n,tn,!1,n.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(n){En[n]=new Un(n,tn,!1,n,null,!1,!1)}),["allowFullScreen","async","autoFocus","autoPlay","controls","default","defer","disabled","disablePictureInPicture","disableRemotePlayback","formNoValidate","hidden","loop","noModule","noValidate","open","playsInline","readOnly","required","reversed","scoped","seamless","itemScope"].forEach(function(n){En[n]=new Un(n,Rt,!1,n.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(n){En[n]=new Un(n,Rt,!0,n,null,!1,!1)}),["capture","download"].forEach(function(n){En[n]=new Un(n,Oe,!1,n,null,!1,!1)}),["cols","rows","size","span"].forEach(function(n){En[n]=new Un(n,_n,!1,n,null,!1,!1)}),["rowSpan","start"].forEach(function(n){En[n]=new Un(n,Vt,!1,n.toLowerCase(),null,!1,!1)});var Sr=/[\-\:]([a-z])/g,Ao=function(n){return n[1].toUpperCase()};["accent-height","alignment-baseline","arabic-form","baseline-shift","cap-height","clip-path","clip-rule","color-interpolation","color-interpolation-filters","color-profile","color-rendering","dominant-baseline","enable-background","fill-opacity","fill-rule","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","glyph-name","glyph-orientation-horizontal","glyph-orientation-vertical","horiz-adv-x","horiz-origin-x","image-rendering","letter-spacing","lighting-color","marker-end","marker-mid","marker-start","overline-position","overline-thickness","paint-order","panose-1","pointer-events","rendering-intent","shape-rendering","stop-color","stop-opacity","strikethrough-position","strikethrough-thickness","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-anchor","text-decoration","text-rendering","underline-position","underline-thickness","unicode-bidi","unicode-range","units-per-em","v-alphabetic","v-hanging","v-ideographic","v-mathematical","vector-effect","vert-adv-y","vert-origin-x","vert-origin-y","word-spacing","writing-mode","xmlns:xlink","x-height"].forEach(function(n){var a=n.replace(Sr,Ao);En[a]=new Un(a,Xe,!1,n,null,!1,!1)}),["xlink:actuate","xlink:arcrole","xlink:role","xlink:show","xlink:title","xlink:type"].forEach(function(n){var a=n.replace(Sr,Ao);En[a]=new Un(a,Xe,!1,n,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(n){var a=n.replace(Sr,Ao);En[a]=new Un(a,Xe,!1,n,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(n){En[n]=new Un(n,Xe,!1,n.toLowerCase(),null,!1,!1)});var as="xlinkHref";En[as]=new Un("xlinkHref",Xe,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(n){En[n]=new Un(n,Xe,!1,n.toLowerCase(),null,!0,!0)});var is=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i,ko=!1;function Oo(n){!ko&&is.test(n)&&(ko=!0,u("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.",JSON.stringify(n)))}function pr(n,a,s,f){if(f.mustUseProperty){var p=f.propertyName;return n[p]}else{sr(s,a),f.sanitizeURL&&Oo(""+s);var y=f.attributeName,C=null;if(f.type===Oe){if(n.hasAttribute(y)){var T=n.getAttribute(y);return T===""?!0:kn(a,s,f,!1)?T:T===""+s?s:T}}else if(n.hasAttribute(y)){if(kn(a,s,f,!1))return n.getAttribute(y);if(f.type===Rt)return s;C=n.getAttribute(y)}return kn(a,s,f,!1)?C===null?s:C:C===""+s?s:C}}function Ma(n,a,s,f){{if(!Nn(a))return;if(!n.hasAttribute(a))return s===void 0?void 0:null;var p=n.getAttribute(a);return sr(s,a),p===""+s?s:p}}function Ii(n,a,s,f){var p=ha(a);if(!Ln(a,p,f)){if(kn(a,s,p,f)&&(s=null),f||p===null){if(Nn(a)){var y=a;s===null?n.removeAttribute(y):(sr(s,a),n.setAttribute(y,""+s))}return}var C=p.mustUseProperty;if(C){var T=p.propertyName;if(s===null){var O=p.type;n[T]=O===Rt?!1:""}else n[T]=s;return}var z=p.attributeName,H=p.attributeNamespace;if(s===null)n.removeAttribute(z);else{var ee=p.type,J;ee===Rt||ee===Oe&&s===!0?J="":(sr(s,z),J=""+s,p.sanitizeURL&&Oo(J.toString())),H?n.setAttributeNS(H,z,J):n.setAttribute(z,J)}}}var Pa=Symbol.for("react.element"),q=Symbol.for("react.portal"),Ue=Symbol.for("react.fragment"),qe=Symbol.for("react.strict_mode"),St=Symbol.for("react.profiler"),an=Symbol.for("react.provider"),Sn=Symbol.for("react.context"),tt=Symbol.for("react.forward_ref"),Ht=Symbol.for("react.suspense"),jn=Symbol.for("react.suspense_list"),zn=Symbol.for("react.memo"),sn=Symbol.for("react.lazy"),zr=Symbol.for("react.scope"),hi=Symbol.for("react.debug_trace_mode"),Do=Symbol.for("react.offscreen"),va=Symbol.for("react.legacy_hidden"),io=Symbol.for("react.cache"),wu=Symbol.for("react.tracing_marker"),mi=Symbol.iterator,$s="@@iterator";function Fi(n){if(n===null||typeof n!="object")return null;var a=mi&&n[mi]||n[$s];return typeof a=="function"?a:null}var Jt=Object.assign,zi=0,ga,Mo,os,Po,ss,ls,Lo;function Io(){}Io.__reactDisabledLog=!0;function us(){{if(zi===0){ga=console.log,Mo=console.info,os=console.warn,Po=console.error,ss=console.group,ls=console.groupCollapsed,Lo=console.groupEnd;var n={configurable:!0,enumerable:!0,value:Io,writable:!0};Object.defineProperties(console,{info:n,log:n,warn:n,error:n,group:n,groupCollapsed:n,groupEnd:n})}zi++}}function Vs(){{if(zi--,zi===0){var n={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Jt({},n,{value:ga}),info:Jt({},n,{value:Mo}),warn:Jt({},n,{value:os}),error:Jt({},n,{value:Po}),group:Jt({},n,{value:ss}),groupCollapsed:Jt({},n,{value:ls}),groupEnd:Jt({},n,{value:Lo})})}zi<0&&u("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var Hs=r.ReactCurrentDispatcher,vi;function Pr(n,a,s){{if(vi===void 0)try{throw Error()}catch(p){var f=p.stack.trim().match(/\n( *(at )?)/);vi=f&&f[1]||""}return` +`+vi+n}}var La=!1,ya;{var Ia=typeof WeakMap=="function"?WeakMap:Map;ya=new Ia}function cs(n,a){if(!n||La)return"";{var s=ya.get(n);if(s!==void 0)return s}var f;La=!0;var p=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var y;y=Hs.current,Hs.current=null,us();try{if(a){var C=function(){throw Error()};if(Object.defineProperty(C.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(C,[])}catch(ve){f=ve}Reflect.construct(n,[],C)}else{try{C.call()}catch(ve){f=ve}n.call(C.prototype)}}else{try{throw Error()}catch(ve){f=ve}n()}}catch(ve){if(ve&&f&&typeof ve.stack=="string"){for(var T=ve.stack.split(` +`),O=f.stack.split(` +`),z=T.length-1,H=O.length-1;z>=1&&H>=0&&T[z]!==O[H];)H--;for(;z>=1&&H>=0;z--,H--)if(T[z]!==O[H]){if(z!==1||H!==1)do if(z--,H--,H<0||T[z]!==O[H]){var ee=` +`+T[z].replace(" at new "," at ");return n.displayName&&ee.includes("")&&(ee=ee.replace("",n.displayName)),typeof n=="function"&&ya.set(n,ee),ee}while(z>=1&&H>=0);break}}}finally{La=!1,Hs.current=y,Vs(),Error.prepareStackTrace=p}var J=n?n.displayName||n.name:"",he=J?Pr(J):"";return typeof n=="function"&&ya.set(n,he),he}function Fo(n,a,s){return cs(n,!0)}function fs(n,a,s){return cs(n,!1)}function Dl(n){var a=n.prototype;return!!(a&&a.isReactComponent)}function ba(n,a,s){if(n==null)return"";if(typeof n=="function")return cs(n,Dl(n));if(typeof n=="string")return Pr(n);switch(n){case Ht:return Pr("Suspense");case jn:return Pr("SuspenseList")}if(typeof n=="object")switch(n.$$typeof){case tt:return fs(n.render);case zn:return ba(n.type,a,s);case sn:{var f=n,p=f._payload,y=f._init;try{return ba(y(p),a,s)}catch{}}}return""}function zo(n){switch(n._debugOwner&&n._debugOwner.type,n._debugSource,n.tag){case _:return Pr(n.type);case fe:return Pr("Lazy");case $:return Pr("Suspense");case Se:return Pr("SuspenseList");case m:case S:case Z:return fs(n.type);case F:return fs(n.type.render);case v:return Fo(n.type);default:return""}}function Fa(n){try{var a="",s=n;do a+=zo(s),s=s.return;while(s);return a}catch(f){return` +Error generating stack: `+f.message+` +`+f.stack}}function gi(n,a,s){var f=n.displayName;if(f)return f;var p=a.displayName||a.name||"";return p!==""?s+"("+p+")":s}function Ws(n){return n.displayName||"Context"}function ln(n){if(n==null)return null;if(typeof n.tag=="number"&&u("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case Ue:return"Fragment";case q:return"Portal";case St:return"Profiler";case qe:return"StrictMode";case Ht:return"Suspense";case jn:return"SuspenseList"}if(typeof n=="object")switch(n.$$typeof){case Sn:var a=n;return Ws(a)+".Consumer";case an:var s=n;return Ws(s._context)+".Provider";case tt:return gi(n,n.render,"ForwardRef");case zn:var f=n.displayName||null;return f!==null?f:ln(n.type)||"Memo";case sn:{var p=n,y=p._payload,C=p._init;try{return ln(C(y))}catch{return null}}}return null}function yi(n,a,s){var f=a.displayName||a.name||"";return n.displayName||(f!==""?s+"("+f+")":s)}function Bo(n){return n.displayName||"Context"}function Ft(n){var a=n.tag,s=n.type;switch(a){case ge:return"Cache";case U:var f=s;return Bo(f)+".Consumer";case L:var p=s;return Bo(p._context)+".Provider";case se:return"DehydratedFragment";case F:return yi(s,s.render,"ForwardRef");case k:return"Fragment";case _:return s;case w:return"Portal";case x:return"Root";case R:return"Text";case fe:return ln(s);case P:return s===qe?"StrictMode":"Mode";case ae:return"Offscreen";case B:return"Profiler";case Ke:return"Scope";case $:return"Suspense";case Se:return"SuspenseList";case _e:return"TracingMarker";case v:case m:case me:case S:case K:case Z:if(typeof s=="function")return s.displayName||s.name||null;if(typeof s=="string")return s;break}return null}var Gs=r.ReactDebugCurrentFrame,xr=null,Bi=!1;function Ha(){{if(xr===null)return null;var n=xr._debugOwner;if(n!==null&&typeof n<"u")return Ft(n)}return null}function oo(){return xr===null?"":Fa(xr)}function Er(){Gs.getCurrentStack=null,xr=null,Bi=!1}function rr(n){Gs.getCurrentStack=n===null?null:oo,xr=n,Bi=!1}function Uo(){return xr}function qr(n){Bi=n}function dr(n){return""+n}function aa(n){switch(typeof n){case"boolean":case"number":case"string":case"undefined":return n;case"object":return gn(n),n;default:return""}}var _u={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0};function so(n,a){_u[a.type]||a.onChange||a.onInput||a.readOnly||a.disabled||a.value==null||u("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."),a.onChange||a.readOnly||a.disabled||a.checked==null||u("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.")}function bi(n){var a=n.type,s=n.nodeName;return s&&s.toLowerCase()==="input"&&(a==="checkbox"||a==="radio")}function Ys(n){return n._valueTracker}function vn(n){n._valueTracker=null}function In(n){var a="";return n&&(bi(n)?a=n.checked?"true":"false":a=n.value),a}function Ml(n){var a=bi(n)?"checked":"value",s=Object.getOwnPropertyDescriptor(n.constructor.prototype,a);gn(n[a]);var f=""+n[a];if(!(n.hasOwnProperty(a)||typeof s>"u"||typeof s.get!="function"||typeof s.set!="function")){var p=s.get,y=s.set;Object.defineProperty(n,a,{configurable:!0,get:function(){return p.call(this)},set:function(T){gn(T),f=""+T,y.call(this,T)}}),Object.defineProperty(n,a,{enumerable:s.enumerable});var C={getValue:function(){return f},setValue:function(T){gn(T),f=""+T},stopTracking:function(){vn(n),delete n[a]}};return C}}function za(n){Ys(n)||(n._valueTracker=Ml(n))}function W(n){if(!n)return!1;var a=Ys(n);if(!a)return!0;var s=a.getValue(),f=In(n);return f!==s?(a.setValue(f),!0):!1}function Q(n){if(n=n||(typeof document<"u"?document:void 0),typeof n>"u")return null;try{return n.activeElement||n.body}catch{return n.body}}var de=!1,at=!1,un=!1,Fn=!1;function Wt(n){var a=n.type==="checkbox"||n.type==="radio";return a?n.checked!=null:n.value!=null}function b(n,a){var s=n,f=a.checked,p=Jt({},a,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:f??s._wrapperState.initialChecked});return p}function A(n,a){so("input",a),a.checked!==void 0&&a.defaultChecked!==void 0&&!at&&(u("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ha()||"A component",a.type),at=!0),a.value!==void 0&&a.defaultValue!==void 0&&!de&&(u("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ha()||"A component",a.type),de=!0);var s=n,f=a.defaultValue==null?"":a.defaultValue;s._wrapperState={initialChecked:a.checked!=null?a.checked:a.defaultChecked,initialValue:aa(a.value!=null?a.value:f),controlled:Wt(a)}}function V(n,a){var s=n,f=a.checked;f!=null&&Ii(s,"checked",f,!1)}function G(n,a){var s=n;{var f=Wt(a);!s._wrapperState.controlled&&f&&!Fn&&(u("A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),Fn=!0),s._wrapperState.controlled&&!f&&!un&&(u("A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),un=!0)}V(n,a);var p=aa(a.value),y=a.type;if(p!=null)y==="number"?(p===0&&s.value===""||s.value!=p)&&(s.value=dr(p)):s.value!==dr(p)&&(s.value=dr(p));else if(y==="submit"||y==="reset"){s.removeAttribute("value");return}a.hasOwnProperty("value")?$e(s,a.type,p):a.hasOwnProperty("defaultValue")&&$e(s,a.type,aa(a.defaultValue)),a.checked==null&&a.defaultChecked!=null&&(s.defaultChecked=!!a.defaultChecked)}function te(n,a,s){var f=n;if(a.hasOwnProperty("value")||a.hasOwnProperty("defaultValue")){var p=a.type,y=p==="submit"||p==="reset";if(y&&(a.value===void 0||a.value===null))return;var C=dr(f._wrapperState.initialValue);s||C!==f.value&&(f.value=C),f.defaultValue=C}var T=f.name;T!==""&&(f.name=""),f.defaultChecked=!f.defaultChecked,f.defaultChecked=!!f._wrapperState.initialChecked,T!==""&&(f.name=T)}function Fe(n,a){var s=n;G(s,a),Ce(s,a)}function Ce(n,a){var s=a.name;if(a.type==="radio"&&s!=null){for(var f=n;f.parentNode;)f=f.parentNode;sr(s,"name");for(var p=f.querySelectorAll("input[name="+JSON.stringify(""+s)+'][type="radio"]'),y=0;y.")))}):a.dangerouslySetInnerHTML!=null&&(At||(At=!0,u("Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.")))),a.selected!=null&&!it&&(u("Use the `defaultValue` or `value` props on must be a scalar value if `multiple` is false.%s",s,Sa())}}}}function On(n,a,s,f){var p=n.options;if(a){for(var y=s,C={},T=0;T.");var f=Jt({},a,{value:void 0,defaultValue:void 0,children:dr(s._wrapperState.initialValue)});return f}function gv(n,a){var s=n;so("textarea",a),a.value!==void 0&&a.defaultValue!==void 0&&!Zy&&(u("%s contains a textarea with both value and defaultValue props. Textarea elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled textarea and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ha()||"A component"),Zy=!0);var f=a.value;if(f==null){var p=a.children,y=a.defaultValue;if(p!=null){u("Use the `defaultValue` or `value` props instead of setting children on