diff --git a/CHANGELOG.md b/CHANGELOG.md index 4babaa918..fdfc2a7db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ To use and of the new models, simply select model from *Networks -> Reference* a for example: set size->before->method:nearest, mode:fixed or mode:fill - control tab includes superset of txt and img scripts - automatically offload disabled controlnet units +- ipadapter option to auto-crop input images to faces to improve efficiency of face-transfter ipadapters - update **IPEX** to 2.1.40+xpu on Linux, thanks @Disty0! - general **ROCm** fixes, thanks @lshqqytiger! - support for HIP SDK 6.1 on ZLUDA backend, thanks @lshqqytiger! diff --git a/cli/api-control.py b/cli/api-control.py index a735bce4f..79667a2e9 100755 --- a/cli/api-control.py +++ b/cli/api-control.py @@ -132,7 +132,7 @@ def generate(args): # pylint: disable=redefined-outer-name if __name__ == "__main__": - parser = argparse.ArgumentParser(description = 'api-img2img') + parser = argparse.ArgumentParser(description = 'api-control') parser.add_argument('--init', required=False, default=None, help='init image') parser.add_argument('--input', required=False, default=None, help='input image') parser.add_argument('--mask', required=False, help='mask image') @@ -148,5 +148,5 @@ if __name__ == "__main__": parser.add_argument('--control', required=False, help='control units') parser.add_argument('--ipadapter', required=False, help='ipadapter units') args = parser.parse_args() - log.info(f'img2img: {args}') + log.info(f'api-control: {args}') generate(args) diff --git a/cli/api-faceid.py b/cli/api-faceid.py index dd9645cea..e656a4a47 100755 --- a/cli/api-faceid.py +++ b/cli/api-faceid.py @@ -95,7 +95,7 @@ if __name__ == "__main__": parser.add_argument('--output', required=False, default=None, help='output image file') parser.add_argument('--model', required=False, help='model name') args = parser.parse_args() - log.info(f'img2img: {args}') + log.info(f'api-faceid: {args}') generate(args) """ diff --git a/cli/api-faces.py b/cli/api-faces.py new file mode 100755 index 000000000..0a98843c0 --- /dev/null +++ b/cli/api-faces.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +import os +import io +import base64 +import logging +import argparse +import requests +import urllib3 +from PIL import Image + +sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860") +sd_username = os.environ.get('SDAPI_USR', None) +sd_password = os.environ.get('SDAPI_PWD', None) + +logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s') +log = logging.getLogger(__name__) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + +def auth(): + if sd_username is not None and sd_password is not None: + return requests.auth.HTTPBasicAuth(sd_username, sd_password) + return None + + +def post(endpoint: str, dct: dict = None): + req = requests.post(f'{sd_url}{endpoint}', json = dct, timeout=300, verify=False, auth=auth()) + if req.status_code != 200: + return { 'error': req.status_code, 'reason': req.reason, 'url': req.url } + else: + return req.json() + + +def encode(f): + image = Image.open(f) + if image.mode == 'RGBA': + image = image.convert('RGB') + with io.BytesIO() as stream: + image.save(stream, 'JPEG') + image.close() + values = stream.getvalue() + encoded = base64.b64encode(values).decode() + return encoded + + +def detect(args): # pylint: disable=redefined-outer-name + data = post('/sdapi/v1/faces', { 'image': encode(args.image) }) + for face in zip(data['images'], data['scores']): + log.info(f'Face: score={face[1]}') + image = Image.open(io.BytesIO(base64.b64decode(face[0]))) + image.save(f'/tmp/face_{face[1]}.jpg') + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description = 'api-faces') + parser.add_argument('--image', required=True, help='input image') + args = parser.parse_args() + log.info(f'api-faces: {args}') + detect(args) diff --git a/cli/api-img2img.py b/cli/api-img2img.py index 3a2961e5b..99ab5ac34 100755 --- a/cli/api-img2img.py +++ b/cli/api-img2img.py @@ -94,5 +94,5 @@ if __name__ == "__main__": parser.add_argument('--output', required=False, default=None, help='output image file') parser.add_argument('--model', required=False, help='model name') args = parser.parse_args() - log.info(f'img2img: {args}') + log.info(f'api-img2img: {args}') generate(args) diff --git a/cli/api-info.py b/cli/api-info.py index 83e4dfe2e..1056ddf2a 100755 --- a/cli/api-info.py +++ b/cli/api-info.py @@ -53,5 +53,5 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description = 'api-info') parser.add_argument('--input', required=True, help='input image') args = parser.parse_args() - log.info(f'info: {args}') + log.info(f'api-info: {args}') info(args) diff --git a/cli/api-json.py b/cli/api-json.py index e8c5270fb..79c0ebc3b 100755 --- a/cli/api-json.py +++ b/cli/api-json.py @@ -38,7 +38,7 @@ def post(endpoint: str, payload: dict = None): if __name__ == "__main__": - parser = argparse.ArgumentParser(description = 'api-txt2img') + parser = argparse.ArgumentParser(description = 'api-json') parser.add_argument('endpoint', nargs=1, help='endpoint') parser.add_argument('json', nargs=1, help='json data or file') args = parser.parse_args() diff --git a/cli/api-mask.py b/cli/api-mask.py index 0a1372138..38aae3018 100755 --- a/cli/api-mask.py +++ b/cli/api-mask.py @@ -79,5 +79,5 @@ if __name__ == "__main__": parser.add_argument('--type', required=False, help='output mask type') parser.add_argument('--output', required=False, help='output image') args = parser.parse_args() - log.info(f'info: {args}') + log.info(f'api-mask: {args}') info(args) diff --git a/cli/api-preprocess.py b/cli/api-preprocess.py index 084f6a0b4..abb6a9d2f 100755 --- a/cli/api-preprocess.py +++ b/cli/api-preprocess.py @@ -72,5 +72,5 @@ if __name__ == "__main__": parser.add_argument('--model', required=True, help='preprocessing model') parser.add_argument('--output', required=False, help='output image') args = parser.parse_args() - log.info(f'info: {args}') + log.info(f'api-preprocess: {args}') info(args) diff --git a/cli/api-txt2img.py b/cli/api-txt2img.py index a00515fe5..fe085e7f1 100755 --- a/cli/api-txt2img.py +++ b/cli/api-txt2img.py @@ -80,5 +80,5 @@ if __name__ == "__main__": parser.add_argument('--output', required=False, default=None, help='output image file') parser.add_argument('--model', required=False, help='model name') args = parser.parse_args() - log.info(f'txt2img: {args}') + log.info(f'api-txt2img: {args}') generate(args) diff --git a/cli/api-upscale.py b/cli/api-upscale.py index 082e008a8..7f188650f 100755 --- a/cli/api-upscale.py +++ b/cli/api-upscale.py @@ -86,5 +86,5 @@ if __name__ == "__main__": parser.add_argument('--upscaler', required=False, default='Nearest', help='upscaler name') parser.add_argument('--scale', required=False, default=2, help='upscaler scale') args = parser.parse_args() - log.info(f'upscale: {args}') + log.info(f'api-upscale: {args}') upscale(args) diff --git a/cli/api-vqa.py b/cli/api-vqa.py index 73de8dbc8..87a3ef2b6 100755 --- a/cli/api-vqa.py +++ b/cli/api-vqa.py @@ -60,5 +60,5 @@ if __name__ == "__main__": parser.add_argument('--model', required=False, help='vqa model') parser.add_argument('--question', required=False, help='question') args = parser.parse_args() - log.info(f'info: {args}') + log.info(f'api-vqa: {args}') info(args) diff --git a/html/reference.json b/html/reference.json index 0f9294ad2..c75d48466 100644 --- a/html/reference.json +++ b/html/reference.json @@ -1,4 +1,29 @@ { + "Juggernaut SD-XL XI": { + "path": "juggernautXL_juggXIByRundiffusion.safetensors@https://civitai.com/api/download/models/782002", + "preview": "juggernautXL_v9Rundiffusionphoto2.jpg", + "desc": "Showcase finetuned model based on Stable diffusion XL", + "extras": "width: 1024, height: 1024, sampler: DEIS, steps: 20, cfg_scale: 6.0" + }, + "Juggernaut SD-XL X Hyper": { + "path": "Juggernaut_X_RunDiffusion_Hyper.safetensors@https://civitai.com/api/download/models/471120", + "preview": "juggernautXL_v9Rundiffusionphoto2.jpg", + "desc": "Showcase finetuned model based on Stable diffusion XL", + "extras": "width: 1024, height: 1024, sampler: DEIS, steps: 20, cfg_scale: 6.0" + }, + "Juggernaut SD-XL IX Lightning": { + "path": "juggernautXL_v9Rdphoto2Lightning.safetensors@https://civitai.com/api/download/models/357609", + "preview": "juggernautXL_v9Rdphoto2Lightning.jpg", + "desc": "Showcase finetuned model based on Stable diffusion XL", + "extras": "width: 1024, height: 1024, sampler: DPM SDE, steps: 6, cfg_scale: 2.0" + }, + "Juggernaut SD Reborn": { + "original": true, + "path": "juggernaut_reborn.safetensors@https://civitai.com/api/download/models/274039", + "preview": "juggernaut_reborn.jpg", + "desc": "Showcase finetuned model based on Stable diffusion 1.5", + "extras": "width: 512, height: 512, sampler: DEIS, steps: 20, cfg_scale: 6.0" + }, "DreamShaper SD v8": { "original": true, @@ -19,31 +44,14 @@ "desc": "Showcase finetuned model based on Stable diffusion XL", "extras": "width: 1024, height: 1024, sampler: DPM SDE, steps: 8, cfg_scale: 2.0" }, - "Juggernaut SD Reborn": { - "original": true, - "path": "juggernaut_reborn.safetensors@https://civitai.com/api/download/models/274039", - "preview": "juggernaut_reborn.jpg", - "desc": "Showcase finetuned model based on Stable diffusion 1.5", - "extras": "width: 512, height: 512, sampler: DEIS, steps: 20, cfg_scale: 6.0" - }, - "Juggernaut SD-XL v9": { - "path": "juggernautXL_v9Rundiffusionphoto2.safetensors@https://civitai.com/api/download/models/348913", - "preview": "juggernautXL_v9Rundiffusionphoto2.jpg", - "desc": "Showcase finetuned model based on Stable diffusion XL", - "extras": "width: 1024, height: 1024, sampler: DEIS, steps: 20, cfg_scale: 6.0" - }, - "Juggernaut SD-XL v9 Lightning": { - "path": "juggernautXL_v9Rdphoto2Lightning.safetensors@https://civitai.com/api/download/models/357609", - "preview": "juggernautXL_v9Rdphoto2Lightning.jpg", - "desc": "Showcase finetuned model based on Stable diffusion XL", - "extras": "width: 1024, height: 1024, sampler: DPM SDE, steps: 6, cfg_scale: 2.0" - }, + "Tempest SD-XL v0.1": { "path": "TempestV0.1-Artistic.safetensors@https://huggingface.co/dataautogpt3/TempestV0.1/resolve/main/TempestV0.1-Artistic.safetensors?download=true", "preview": "TempestV0.1-Artistic.jpg", "desc": "The TempestV0.1 Initiative is a powerhouse in image generation, leveraging an unparalleled dataset of over 6 million images. The collection's vast scale, with resolutions from 1400x2100 to 4800x7200, encompasses 200GB of high-quality content.", "extras": "width: 2048, height: 1024, sampler: DEIS, steps: 40, cfg_scale: 6.0" }, + "SDXS DreamShaper 512": { "path": "IDKiro/sdxs-512-dreamshaper", "preview": "IDKiro--sdxs-512-dreamshaper.jpg", @@ -152,7 +160,7 @@ "AuraFlow 0.3": { "path": "fal/AuraFlow-v0.3", "desc": "AuraFlow v0.3 is the fully open-sourced flow-based text-to-image generation model. The model was trained with more compute compared to the previous version, AuraFlow-v0.2. Compared to AuraFlow-v0.2, the model is fine-tuned on more aesthetic datasets and now supports various aspect ratio, (now width and height up to 1536 pixels).", - "preview": "fal--AuraFlow.jpg", + "preview": "fal--AuraFlow-v0.3.jpg", "skip": true, "extras": "width: 1024, height: 1024" }, diff --git a/models/Reference/PixArt-alpha--PixArt-Sigma-XL-2-1024-MS.jpg b/models/Reference/PixArt-alpha--PixArt-Sigma-XL-2-1024-MS.jpg new file mode 100644 index 000000000..d1a808760 Binary files /dev/null and b/models/Reference/PixArt-alpha--PixArt-Sigma-XL-2-1024-MS.jpg differ diff --git a/models/Reference/PixArt-alpha--PixArt-XL-2-512x512 - Copy.jpg:Zone.Identifier b/models/Reference/PixArt-alpha--PixArt-XL-2-512x512 - Copy.jpg:Zone.Identifier new file mode 100644 index 000000000..e69de29bb diff --git a/models/Reference/fal--AuraFlow.jpg b/models/Reference/fal--AuraFlow-v0.3.jpg similarity index 100% rename from models/Reference/fal--AuraFlow.jpg rename to models/Reference/fal--AuraFlow-v0.3.jpg diff --git a/modules/api/api.py b/modules/api/api.py index e4dce4cec..890a18733 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -55,6 +55,7 @@ class Api: self.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=models.ResProcessBatch) self.add_api_route("/sdapi/v1/preprocess", self.process.post_preprocess, methods=["POST"]) self.add_api_route("/sdapi/v1/mask", self.process.post_mask, methods=["POST"]) + self.add_api_route("/sdapi/v1/faces", self.process.post_face, methods=["POST"]) # api dealing with optional scripts self.add_api_route("/sdapi/v1/scripts", script.get_scripts_list, methods=["GET"], response_model=models.ResScripts) diff --git a/modules/api/control.py b/modules/api/control.py index b90dba049..9c93b6bc6 100644 --- a/modules/api/control.py +++ b/modules/api/control.py @@ -95,7 +95,7 @@ class APIControl(): def prepare_ip_adapter(self, request): if hasattr(request, "ip_adapter") and request.ip_adapter: - args = { 'ip_adapter_names': [], 'ip_adapter_scales': [], 'ip_adapter_starts': [], 'ip_adapter_ends': [], 'ip_adapter_images': [], 'ip_adapter_masks': [] } + args = { 'ip_adapter_names': [], 'ip_adapter_scales': [], 'ip_adapter_crops': [], 'ip_adapter_starts': [], 'ip_adapter_ends': [], 'ip_adapter_images': [], 'ip_adapter_masks': [] } for ipadapter in request.ip_adapter: if not ipadapter.images or len(ipadapter.images) == 0: continue diff --git a/modules/api/generate.py b/modules/api/generate.py index 371f3c0bf..aeafa05a0 100644 --- a/modules/api/generate.py +++ b/modules/api/generate.py @@ -64,6 +64,7 @@ class APIGenerate(): if hasattr(request, "ip_adapter") and request.ip_adapter: p.ip_adapter_names = [] p.ip_adapter_scales = [] + p.ip_adapter_crops = [] p.ip_adapter_starts = [] p.ip_adapter_ends = [] p.ip_adapter_images = [] @@ -72,6 +73,7 @@ class APIGenerate(): continue p.ip_adapter_names.append(ipadapter.adapter) p.ip_adapter_scales.append(ipadapter.scale) + p.ip_adapter_crops.append(ipadapter.crop) p.ip_adapter_starts.append(ipadapter.start) p.ip_adapter_ends.append(ipadapter.end) p.ip_adapter_images.append([helpers.decode_base64_to_image(x) for x in ipadapter.images]) diff --git a/modules/api/process.py b/modules/api/process.py index 830aa14d9..6dd5e701e 100644 --- a/modules/api/process.py +++ b/modules/api/process.py @@ -26,6 +26,13 @@ class ReqMask(BaseModel): model: Optional[str] = Field(title="Model", description="The model to use for preprocessing") params: Optional[dict] = Field(default={}, title="Settings", description="Preprocessor settings") +class ReqFace(BaseModel): + image: str = Field(title="Image", description="The base64 encoded image") + +class ResFace(BaseModel): + images: List[str] = Field(title="Image", description="The base64 encoded images of detected faces") + scores: List[float] = Field(title="Scores", description="The scores of the detected faces") + class ResMask(BaseModel): mask: str = Field(default='', title="Image", description="The processed image in base64 format") @@ -98,3 +105,18 @@ class APIProcess(): return JSONResponse(status_code=400, content={"error": "Mask is none"}) image = encode_pil_to_base64(processed) return ResMask(mask=image) + + def post_face(self, req: ReqFace): + from scripts.face_details import yolo # pylint: disable=no-name-in-module + image = decode_base64_to_image(req.image) + shared.state.begin('API-FACE', api=True) + images = [] + scores = [] + with self.queue_lock: + yolo.load() + faces = yolo.predict(image) + for face in faces: + images.append(encode_pil_to_base64(face.face)) + scores.append(face.score) + shared.state.end(api=False) + return ResFace(images=images, scores=scores) diff --git a/modules/control/processors.py b/modules/control/processors.py index 6c405ce59..92cf61629 100644 --- a/modules/control/processors.py +++ b/modules/control/processors.py @@ -212,7 +212,6 @@ class Processor(): def __call__(self, image_input: Image, mode: str = 'RGB', resize_mode: int = 0, resize_name: str = 'None', scale_tab: int = 1, scale_by: float = 1.0, local_config: dict = {}): if self.override is not None: debug(f'Control Processor: id="{self.processor_id}" override={self.override}') - print('HERE1', image_input) if image_input is not None and image_input.size != self.override.size: debug(f'Control resize: op=override image={self.override} width={image_input.width} height={image_input.height} mode={resize_mode} name={resize_name}') image_input = images.resize_image(resize_mode, self.override, image_input.width, image_input.height, resize_name) @@ -224,7 +223,6 @@ class Processor(): debug(f'Control resize: op=before image={image_input} width={width_before} height={height_before} mode={resize_mode} name={resize_name}') image_input = images.resize_image(resize_mode, image_input, width_before, height_before, resize_name) if self.processor_id is None or self.processor_id == 'None': - print('HERE3', self.override) return image_input image_process = image_input if image_input is None: diff --git a/modules/ipadapter.py b/modules/ipadapter.py index f1fa60197..e8476a1b7 100644 --- a/modules/ipadapter.py +++ b/modules/ipadapter.py @@ -72,6 +72,34 @@ def get_scales(adapter_scales, adapter_images): return output_scales +def get_crops(adapter_crops, adapter_images): + output_crops = [adapter_crops] if not isinstance(adapter_crops, list) else adapter_crops + while len(output_crops) < len(adapter_images): + output_crops.append(output_crops[-1]) + return output_crops + + +def crop_images(images, crops): + try: + for i in range(len(images)): + if crops[i]: + from scripts.face_details import yolo # pylint: disable=no-name-in-module + yolo.load() + cropped = [] + for image in images[i]: + faces = yolo.predict(image) + if len(faces) > 0: + cropped.append(faces[0].face) + if len(cropped) == len(images[i]): + print('HERE0') + images[i] = cropped + else: + shared.log.error(f'IP adapter: failed to crop image: source={len(images[i])} faces={len(cropped)}') + except Exception as e: + shared.log.error(f'IP adapter: failed to crop image: {e}') + return images + + def unapply(pipe): # pylint: disable=arguments-differ try: if hasattr(pipe, 'set_ip_adapter_scale'): @@ -84,7 +112,7 @@ def unapply(pipe): # pylint: disable=arguments-differ pass -def apply(pipe, p: processing.StableDiffusionProcessing, adapter_names=[], adapter_scales=[1.0], adapter_starts=[0.0], adapter_ends=[1.0], adapter_images=[]): +def apply(pipe, p: processing.StableDiffusionProcessing, adapter_names=[], adapter_scales=[1.0], adapter_crops=[False], adapter_starts=[0.0], adapter_ends=[1.0], adapter_images=[]): global clip_loaded # pylint: disable=global-statement # overrides if hasattr(p, 'ip_adapter_names'): @@ -104,6 +132,8 @@ def apply(pipe, p: processing.StableDiffusionProcessing, adapter_names=[], adapt return False if hasattr(p, 'ip_adapter_scales'): adapter_scales = p.ip_adapter_scales + if hasattr(p, 'ip_adapter_crops'): + adapter_crops = p.ip_adapter_crops if hasattr(p, 'ip_adapter_starts'): adapter_starts = p.ip_adapter_starts if hasattr(p, 'ip_adapter_ends'): @@ -131,6 +161,8 @@ def apply(pipe, p: processing.StableDiffusionProcessing, adapter_names=[], adapt return False adapter_scales = get_scales(adapter_scales, adapter_images) p.ip_adapter_scales = adapter_scales.copy() + adapter_crops = get_crops(adapter_crops, adapter_images) + p.ip_adapter_crops = adapter_crops.copy() adapter_starts = get_scales(adapter_starts, adapter_images) p.ip_adapter_starts = adapter_starts.copy() adapter_ends = get_scales(adapter_ends, adapter_images) @@ -202,7 +234,7 @@ def apply(pipe, p: processing.StableDiffusionProcessing, adapter_names=[], adapt adapter_scales[i] = 0.00 pipe.set_ip_adapter_scale(adapter_scales) ip_str = [f'{os.path.splitext(adapter)[0]}:{scale}:{start}:{end}' for adapter, scale, start, end in zip(adapter_names, adapter_scales, adapter_starts, adapter_ends)] - p.task_args['ip_adapter_image'] = adapter_images + p.task_args['ip_adapter_image'] = crop_images(adapter_images, adapter_crops) if len(adapter_masks) > 0: p.cross_attention_kwargs = { 'ip_adapter_masks': adapter_masks } p.extra_generation_params["IP Adapter"] = ';'.join(ip_str) diff --git a/modules/processing.py b/modules/processing.py index 173d85564..5c7390903 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -328,7 +328,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.scripts.postprocess_batch_list(p, batch_params, batch_number=n) x_samples_ddim = batch_params.images - def infotext(index): # pylint: disable=function-redefined # noqa: F811 + def infotext(index): # pylint: disable=function-redefined return create_infotext(p, p.prompts, p.seeds, p.subseeds, index=index, all_negative_prompts=p.negative_prompts) for i, x_sample in enumerate(x_samples_ddim): diff --git a/modules/processing_class.py b/modules/processing_class.py index e61cbe401..ad083ec5a 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -122,6 +122,7 @@ class StableDiffusionProcessing: self.ip_adapter_images = [] self.ip_adapter_starts = [0.0] self.ip_adapter_ends = [1.0] + self.ip_adapter_crops = [] # hdr self.hdr_mode=hdr_mode self.hdr_brightness=hdr_brightness diff --git a/modules/sd_models.py b/modules/sd_models.py index 8c07d022d..0a3ceca81 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -211,7 +211,8 @@ def get_closet_checkpoint_match(search_string): if found and len(found) > 0: return found[0] for v in shared.reference_models.values(): - if search_string in v['path'] or os.path.basename(search_string) in v['path']: + pth = v['path'].split('@')[-1] + if search_string in pth or os.path.basename(search_string) in pth: model_name = search_string.replace('huggingface/', '') checkpoint_info = CheckpointInfo(v['path']) # create a virutal model info checkpoint_info.type = 'huggingface' diff --git a/scripts/face_details.py b/scripts/face_details.py index 1a1fce944..004c775f6 100644 --- a/scripts/face_details.py +++ b/scripts/face_details.py @@ -7,10 +7,11 @@ from modules import devices, processing_class class YoLoResult: - def __init__(self, score: float, box: list[int], mask: Image.Image = None, size: float = 0, width = 0, height = 0, args = {}): + def __init__(self, score: float, box: list[int], mask: Image.Image = None, face: Image.Image = None, size: float = 0, width = 0, height = 0, args = {}): self.score = score self.box = box self.mask = mask + self.face = face self.size = size self.width = width self.height = height @@ -82,7 +83,8 @@ class FaceRestorerYolo(FaceRestoration): mask_image = Image.new('L', image.size, 0) draw = ImageDraw.Draw(mask_image) draw.rectangle(box, fill="white", outline=None, width=0) - result.append(YoLoResult(score=round(score, 2), box=box, mask=mask_image, size=size, width=w, height=h, args=args)) + face_image = image.crop(box) + result.append(YoLoResult(score=round(score, 2), box=box, mask=mask_image, face=face_image, size=size, width=w, height=h, args=args)) return result def load(self): diff --git a/scripts/ipadapter.py b/scripts/ipadapter.py index dab1e0fba..c7fcb3053 100644 --- a/scripts/ipadapter.py +++ b/scripts/ipadapter.py @@ -51,6 +51,7 @@ class Script(scripts.Script): starts = [] ends = [] files = [] + crops = [] masks = [] image_galleries = [] mask_galleries = [] @@ -61,6 +62,7 @@ class Script(scripts.Script): with gr.Row(): adapters.append(gr.Dropdown(label='Adapter', choices=list(ipadapter.ADAPTERS), value='None')) scales.append(gr.Slider(label='Scale', minimum=0.0, maximum=1.0, step=0.01, value=0.5)) + crops.append(gr.Checkbox(label='Crop', default=False, interactive=True)) with gr.Row(): starts.append(gr.Slider(label='Start', minimum=0.0, maximum=1.0, step=0.1, value=0)) ends.append(gr.Slider(label='End', minimum=0.0, maximum=1.0, step=0.1, value=1)) @@ -80,7 +82,7 @@ class Script(scripts.Script): layers_label = gr.HTML('InstantStyle: advanced layer activation', visible=False) layers = gr.Text(label='Layer scales', placeholder='{\n"down": {"block_2": [0.0, 1.0]},\n"up": {"block_0": [0.0, 1.0, 0.0]}\n}', rows=1, type='text', interactive=True, lines=5, visible=False, show_label=False) layers_active.change(fn=self.display_advanced, inputs=[layers_active], outputs=[layers_label, layers]) - return [num_adapters] + adapters + scales + files + starts + ends + masks + [layers_active] + [layers] + return [num_adapters] + adapters + scales + files + crops + starts + ends + masks + [layers_active] + [layers] def process(self, p: processing.StableDiffusionProcessing, *args): # pylint: disable=arguments-differ if not shared.native: @@ -95,14 +97,16 @@ class Script(scripts.Script): p.ip_adapter_scales = args[MAX_ADAPTERS:MAX_ADAPTERS*2][:units] if getattr(p, 'ip_adapter_images', []) == []: p.ip_adapter_images = args[MAX_ADAPTERS*2:MAX_ADAPTERS*3][:units] + if getattr(p, 'ip_adapter_crops', []) == []: + p.ip_adapter_crops = args[MAX_ADAPTERS*3:MAX_ADAPTERS*4][:units] if getattr(p, 'ip_adapter_starts', [0.0]) == [0.0]: - p.ip_adapter_starts = args[MAX_ADAPTERS*3:MAX_ADAPTERS*4][:units] + p.ip_adapter_starts = args[MAX_ADAPTERS*4:MAX_ADAPTERS*5][:units] if getattr(p, 'ip_adapter_ends', [1.0]) == [1.0]: - p.ip_adapter_ends = args[MAX_ADAPTERS*4:MAX_ADAPTERS*5][:units] + p.ip_adapter_ends = args[MAX_ADAPTERS*5:MAX_ADAPTERS*6][:units] if getattr(p, 'ip_adapter_masks', []) == []: - p.ip_adapter_masks = args[MAX_ADAPTERS*5:MAX_ADAPTERS*6][:units] + p.ip_adapter_masks = args[MAX_ADAPTERS*6:MAX_ADAPTERS*7][:units] p.ip_adapter_masks = [x for x in p.ip_adapter_masks if x] - layers_active, layers = args[MAX_ADAPTERS*6:MAX_ADAPTERS*7] + layers_active, layers = args[MAX_ADAPTERS*7:MAX_ADAPTERS*8] if layers_active and len(layers) > 0: try: layers = json.loads(layers)