ipadapter optional face autocrop input image

This commit is contained in:
Vladimir Mandic
2024-08-29 14:09:22 -04:00
parent db9de0df42
commit 68a8ffa3cf
27 changed files with 176 additions and 45 deletions
+1
View File
@@ -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!
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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)
"""
+59
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+28 -20
View File
@@ -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"
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

+1
View File
@@ -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)
+1 -1
View File
@@ -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
+2
View File
@@ -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])
+22
View File
@@ -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)
-2
View File
@@ -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:
+34 -2
View File
@@ -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)
+1 -1
View File
@@ -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):
+1
View File
@@ -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
+2 -1
View File
@@ -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'
+4 -2
View File
@@ -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):
+9 -5
View File
@@ -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('<a href="https://huggingface.co/docs/diffusers/main/en/using-diffusers/ip_adapter#style--layout-control" target="_blank">InstantStyle: advanced layer activation</a>', 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)