cleanup api

This commit is contained in:
Vladimir Mandic
2024-03-02 08:59:31 -05:00
parent ba86730f52
commit c29b76fde3
13 changed files with 248 additions and 110 deletions
+8 -6
View File
@@ -5,8 +5,7 @@ from fastapi import FastAPI, APIRouter, Depends, Request
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.exceptions import HTTPException
from modules import errors, shared, postprocessing
from modules.api import models, endpoints, script, helpers, server, nvml, generate
from modules.control import api as control_api
from modules.api import models, endpoints, script, helpers, server, nvml, generate, process, control
errors.install()
@@ -29,6 +28,8 @@ class Api:
self.app = app
self.queue_lock = queue_lock
self.generate = generate.APIGenerate(queue_lock)
self.process = process.APIProcess(queue_lock)
self.control = control.APIControl(queue_lock)
# server api
self.add_api_route("/sdapi/v1/motd", server.get_motd, methods=["GET"], response_model=str)
@@ -49,18 +50,19 @@ class Api:
# core api using locking
self.add_api_route("/sdapi/v1/txt2img", self.generate.post_text2img, methods=["POST"], response_model=models.ResTxt2Img)
self.add_api_route("/sdapi/v1/img2img", self.generate.post_img2img, methods=["POST"], response_model=models.ResImg2Img)
self.add_api_route("/sdapi/v1/control", self.control.post_control, methods=["POST"], response_model=control.ResControl)
self.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=models.ResProcessImage)
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", control_api.post_preprocess, methods=["POST"])
self.add_api_route("/sdapi/v1/mask", control_api.post_mask, methods=["POST"])
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"])
# api dealing with optional scripts
self.add_api_route("/sdapi/v1/scripts", script.get_scripts_list, methods=["GET"], response_model=models.ResScripts)
self.add_api_route("/sdapi/v1/script-info", script.get_script_info, methods=["GET"], response_model=List[models.ItemScript])
# enumerator api
self.add_api_route("/sdapi/v1/preprocessors", control_api.get_preprocess, methods=["GET"])
self.add_api_route("/sdapi/v1/masking", control_api.get_mask, methods=["GET"])
self.add_api_route("/sdapi/v1/preprocessors", self.process.get_preprocess, methods=["GET"], response_model=List[process.ItemPreprocess])
self.add_api_route("/sdapi/v1/masking", self.process.get_mask, methods=["GET"], response_model=process.ItemMask)
self.add_api_route("/sdapi/v1/interrogate", endpoints.get_interrogate, methods=["GET"], response_model=List[str])
self.add_api_route("/sdapi/v1/samplers", endpoints.get_samplers, methods=["GET"], response_model=List[models.ItemSampler])
self.add_api_route("/sdapi/v1/upscalers", endpoints.get_upscalers, methods=["GET"], response_model=List[models.ItemUpscaler])
+114
View File
@@ -0,0 +1,114 @@
from typing import Optional, List
from threading import Lock
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
from modules import errors, shared, scripts, ui
from modules.api import script, helpers
from modules.processing import StableDiffusionProcessingControl
from modules.control import run as run_control
# TODO control api
# should use control.run, not process_images directly
errors.install()
class ReqControl(BaseModel):
pass
class ResControl(BaseModel):
images: List[str] = Field(default=None, title="Image", description="The generated images in base64 format.")
params: dict = Field(default={}, title="Settings", description="Process settings")
info: str = Field(default="", title="Info", description="Process info")
class APIControl():
def __init__(self, queue_lock: Lock):
self.queue_lock = queue_lock
self.default_script_arg = []
def sanitize_args(self, args: dict):
args = vars(args)
args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model
args.pop('script_name', None)
args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them
args.pop('alwayson_scripts', None)
args.pop('face', None)
args.pop('face_id', None)
args.pop('ip_adapter', None)
args.pop('save_images', None)
return args
def sanitize_b64(self, request):
def sanitize_str(args: list):
for idx in range(0, len(args)):
if isinstance(args[idx], str) and len(args[idx]) >= 1000:
args[idx] = f"<str {len(args[idx])}>"
if hasattr(request, "alwayson_scripts") and request.alwayson_scripts:
for script_name in request.alwayson_scripts.keys():
script_obj = request.alwayson_scripts[script_name]
if script_obj and "args" in script_obj and script_obj["args"]:
sanitize_str(script_obj["args"])
if hasattr(request, "script_args") and request.script_args:
sanitize_str(request.script_args)
def prepare_face_module(self, request):
if hasattr(request, "face") and request.face and not request.script_name and (not request.alwayson_scripts or "face" not in request.alwayson_scripts.keys()):
request.script_name = "face"
request.script_args = [
request.face.mode,
request.face.source_images,
request.face.ip_model,
request.face.ip_override_sampler,
request.face.ip_cache_model,
request.face.ip_strength,
request.face.ip_structure,
request.face.id_strength,
request.face.id_conditioning,
request.face.id_cache,
request.face.pm_trigger,
request.face.pm_strength,
request.face.pm_start,
request.face.fs_cache
]
del request.face
def post_control(self, req: ReqControl):
self.prepare_face_module(req)
# prepare script
script_runner = scripts.scripts_control
if not script_runner.scripts:
script_runner.initialize_scripts(False)
ui.create_ui(None)
if not self.default_script_arg:
self.default_script_arg = script.init_default_script_args(script_runner)
# prepare args
args = req.copy(update={ # Override __init__ params
"sampler_name": helpers.validate_sampler_name(req.sampler_name or req.sampler_index),
"sampler_index": None,
"do_not_save_samples": not req.save_images,
"do_not_save_grid": not req.save_images,
"init_images": [helpers.decode_base64_to_image(x) for x in req.init_images] if req.init_images else None,
"mask": helpers.decode_base64_to_image(req.mask) if req.mask else None,
})
args = self.sanitize_args(args)
send_images = args.pop('send_images', True)
# run
with self.queue_lock:
shared.state.begin('api-control', api=True)
# selectable_scripts, selectable_script_idx = script.get_selectable_script(req.script_name, script_runner)
# script_args = script.init_script_args(p, req, self.default_script_arg, selectable_scripts, selectable_script_idx, script_runner)
# output_images, _processed_images, output_info = run_control(**args, **script_args)
output_images = None
output_info = None
shared.state.end(api=False)
# return
b64images = list(map(helpers.encode_pil_to_base64, output_images)) if send_images else []
self.sanitize_b64(req)
return ResControl(images=b64images, params=vars(req), info=output_info)
+2 -2
View File
@@ -1,5 +1,5 @@
from threading import Lock
from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse
from modules import errors, shared, scripts, ui
from modules.api import models, script, helpers
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
@@ -100,7 +100,7 @@ class APIGenerate():
self.prepare_face_module(img2imgreq)
init_images = img2imgreq.init_images
if init_images is None:
raise HTTPException(status_code=404, detail="Init image not found")
return JSONResponse(status_code=400, content={"error": "Init image is none"})
mask = img2imgreq.mask
if mask:
mask = helpers.decode_base64_to_image(mask)
+2 -2
View File
@@ -208,7 +208,7 @@ ReqTxt2Img = PydanticModelGenerator(
StableDiffusionTxt2ImgProcessingAPI = ReqTxt2Img
class ResTxt2Img(BaseModel):
images: List[str] = Field(default=None, title="Image", description="The generated image in base64 format.")
images: List[str] = Field(default=None, title="Image", description="The generated images in base64 format.")
parameters: dict
info: str
@@ -233,7 +233,7 @@ ReqImg2Img = PydanticModelGenerator(
StableDiffusionImg2ImgProcessingAPI = ReqImg2Img
class ResImg2Img(BaseModel):
images: List[str] = Field(default=None, title="Image", description="The generated image in base64 format.")
images: List[str] = Field(default=None, title="Image", description="The generated images in base64 format.")
parameters: dict
info: str
+100
View File
@@ -0,0 +1,100 @@
from typing import Optional, List
from threading import Lock
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
from fastapi.responses import JSONResponse
from modules.api.helpers import decode_base64_to_image, encode_pil_to_base64
from modules import errors, shared
processor = None # cached instance of processor
errors.install()
class ReqPreprocess(BaseModel):
image: str = Field(title="Image", description="The base64 encoded image")
model: str = Field(title="Model", description="The model to use for preprocessing")
params: Optional[dict] = Field(default={}, title="Settings", description="Preprocessor settings")
class ResPreprocess(BaseModel):
model: str = Field(default='', title="Model", description="The processor model used")
image: str = Field(default='', title="Image", description="The processed image in base64 format")
class ReqMask(BaseModel):
image: str = Field(title="Image", description="The base64 encoded image")
type: str = Field(title="Mask type", description="Type of masking image to return")
mask: Optional[str] = Field(title="Mask", description="If optional maks image is not provided auto-masking will be performed")
model: Optional[str] = Field(title="Model", description="The model to use for preprocessing")
params: Optional[dict] = Field(default={}, title="Settings", description="Preprocessor settings")
class ResMask(BaseModel):
mask: str = Field(default='', title="Image", description="The processed image in base64 format")
class ItemPreprocess(BaseModel):
name: str = Field(title="Name")
params: dict = Field(title="Params")
class ItemMask(BaseModel):
models: List[str] = Field(title="Models")
colormaps: List[str] = Field(title="Color maps")
params: dict = Field(title="Params")
types: List[str] = Field(title="Types")
class APIProcess():
def __init__(self, queue_lock: Lock):
self.queue_lock = queue_lock
def get_preprocess(self):
from modules.control import processors
items = []
for k, v in processors.config.items():
items.append(ItemPreprocess(name=k, params=v.get('params', {})))
return items
def post_preprocess(self, req: ReqPreprocess):
global processor # pylint: disable=global-statement
from modules.control import processors
models = list(processors.config)
if req.model not in models:
return JSONResponse(status_code=400, content={"error": f"Processor model not found: id={req.model}"})
image = decode_base64_to_image(req.image)
if processor is None or processor.processor_id != req.model:
with self.queue_lock:
processor = processors.Processor(req.model)
for k, v in req.params.items():
if k not in processors.config[processor.processor_id]['params']:
return JSONResponse(status_code=400, content={"error": f"Processor invalid parameter: id={req.model} {k}={v}"})
shared.state.begin('api-preprocess', api=True)
processed = processor(image, local_config=req.params)
image = encode_pil_to_base64(processed)
shared.state.end(api=False)
return ResPreprocess(model=processor.processor_id, image=image)
def get_mask(self):
from modules import masking
return ItemMask(models=list(masking.MODELS), colormaps=masking.COLORMAP, params=vars(masking.opts), types=masking.TYPES)
def post_mask(self, req: ReqMask):
from modules import masking
if req.model:
if req.model not in masking.MODELS:
return JSONResponse(status_code=400, content={"error": f"Mask model not found: id={req.model}"})
else:
masking.init_model(req.model)
if req.type not in masking.TYPES:
return JSONResponse(status_code=400, content={"error": f"Mask type not found: id={req.type}"})
image = decode_base64_to_image(req.image)
mask = decode_base64_to_image(req.mask) if req.mask else None
for k, v in req.params.items():
if not hasattr(masking.opts, k):
return JSONResponse(status_code=400, content={"error": f"Mask invalid parameter: {k}={v}"})
else:
setattr(masking.opts, k, v)
shared.state.begin('api-mask', api=True)
with self.queue_lock:
processed = masking.run_mask(input_image=image, input_mask=mask, return_type=req.type)
shared.state.end(api=False)
if processed is None:
return JSONResponse(status_code=400, content={"error": "Mask is none"})
image = encode_pil_to_base64(processed)
return ResMask(mask=image)