add preprocess api

This commit is contained in:
Vladimir Mandic
2024-03-01 12:52:07 -05:00
parent d077057417
commit c7d5096f7e
7 changed files with 126 additions and 6 deletions
+2
View File
@@ -41,6 +41,8 @@
- add **ROCm** 6.0 nightly option to installer, thanks @jicka
- support models with their own YAML model config files
- support models with their own JSON per-component config files, for example: `playground-v2.5_vae.config`
- **API**
- add preprocessor api endpoints: `/sdapi/v1/preprocessors`, `/sdapi/v1/preprocess`
- **Internal**
- remove obsolete textual inversion training code
- remove obsolete hypernetworks training code
+1 -2
View File
@@ -9,7 +9,7 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma
- ipadapter masking: <https://github.com/huggingface/diffusers/pull/6847>
- x-adapter: <https://github.com/showlab/X-Adapter>
- async lowvram: <https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14855>
- init latents: variations, tiling, img2img
- init latents: variations, img2img
- diffusers public callbacks
- remove builtin: controlnet
- remove builtin: image-browser
@@ -19,4 +19,3 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma
- second pass: <https://github.com/vladmandic/automatic/issues/2783>
- control api
- masking api
- preprocess api
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python
import io
import os
import time
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 get(endpoint: str, dct: dict = None):
req = requests.get(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 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 info(args): # pylint: disable=redefined-outer-name
t0 = time.time()
with open(args.input, 'rb') as f:
content = f.read()
models = get('/sdapi/v1/preprocessors')
log.info(f'models: {models}')
req = {
'model': 'Canny',
'image': base64.b64encode(content).decode(),
'config': { 'low_threshold': 50 },
}
data = post('/sdapi/v1/preprocess', req)
t1 = time.time()
if 'image' in data:
b64 = data['image'].split(',',1)[0]
image = Image.open(io.BytesIO(base64.b64decode(b64)))
log.info(f'received image: size={image.size} time={t1-t0:.2f}')
else:
log.info(f'received: {data} time={t1-t0:.2f}')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description = 'simple-info')
parser.add_argument('--input', required=True, help='input image')
args = parser.parse_args()
log.info(f'info: {args}')
info(args)
+4 -2
View File
@@ -6,6 +6,7 @@ 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
errors.install()
@@ -43,14 +44,15 @@ class Api:
self.add_api_route("/sdapi/v1/options", server.get_config, methods=["GET"], response_model=models.OptionsModel)
self.add_api_route("/sdapi/v1/options", server.set_config, methods=["POST"])
self.add_api_route("/sdapi/v1/cmd-flags", server.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel)
app.add_api_route("/sdapi/v1/nvml", nvml.get_nvml, methods=["GET"], response_model=List[models.ResNVML])
self.add_api_route("/sdapi/v1/nvml", nvml.get_nvml, methods=["GET"], response_model=List[models.ResNVML])
# 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/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/preprocessors", control_api.get_preprocess, methods=["GET"])
# 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
@@ -272,7 +272,7 @@ class ResProcessBatch(ResProcess):
images: List[str] = Field(title="Images", description="The generated images in base64 format.")
class ReqImageInfo(BaseModel):
image: str = Field(title="Image", description="The base64 encoded PNG image")
image: str = Field(title="Image", description="The base64 encoded image")
class ResImageInfo(BaseModel):
info: str = Field(title="Image info", description="A string with the parameters used to generate the image")
+44
View File
@@ -0,0 +1,44 @@
from typing import Optional
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse
from modules.api.helpers import decode_base64_to_image, encode_pil_to_base64
processor = None # cached instance of processor
class ReqPreprocess(BaseModel):
image: str = Field(default=None, title="Image", description="The base64 encoded image")
model: str = Field(default=None, 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=None, title="Model", description="The processor model used")
image: str = Field(default=None, title="Image", description="The processed image in base64 format")
def get_preprocess():
from modules.control import processors
p = {}
for k, v in processors.config.items():
p[k] = v.get('params')
return JSONResponse(p)
def post_preprocess(req: ReqPreprocess):
global processor # pylint: disable=global-statement
from modules.control import processors
models = list(processors.config)
if req.model not in models:
raise HTTPException(status_code=404, detail=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:
processor = processors.Processor(req.model)
for k, v in req.params.items():
if k not in processors.config[processor.processor_id]['params']:
raise HTTPException(status_code=400, detail=f"Processor invalid parameter: id={req.model} {k}={v}")
processed = processor(image, local_config=req.params)
image = encode_pil_to_base64(processed)
return ResPreprocess(model=processor.processor_id, image=image)
+3 -1
View File
@@ -206,7 +206,7 @@ class Processor():
display(e, 'Control Processor load')
return f'Processor load filed: {processor_id}'
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):
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.processor_id is None or self.processor_id == 'None':
return image_input
if self.override is not None:
@@ -232,6 +232,8 @@ class Processor():
try:
t0 = time.time()
kwargs = config.get(self.processor_id, {}).get('params', None)
if kwargs:
kwargs.update(local_config)
if self.resize:
image_resized = image_input.resize((512, 512), Image.Resampling.LANCZOS)
else: