mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 09:14:35 +02:00
add preprocess api
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+71
@@ -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
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user