1
0
Fork 0
onnx-web/api/onnx_web/output.py

194 lines
5.4 KiB
Python

from hashlib import sha256
from json import dumps
from logging import getLogger
from time import time
from typing import List, Optional
from piexif import ExifIFD, ImageIFD, dump
from piexif.helper import UserComment
from PIL import Image, PngImagePlugin
from .chain.result import ImageMetadata, StageResult
from .params import ImageParams, Param, Size
from .server import ServerContext
from .utils import base_join, hash_value
logger = getLogger(__name__)
def make_output_names(
server: ServerContext,
job_name: str,
count: int = 1,
offset: int = 0,
extension: Optional[str] = None,
suffix: Optional[str] = None,
) -> List[str]:
if suffix is not None:
job_name = f"{job_name}_{suffix}"
return [
f"{job_name}_{i}.{extension or server.image_format}"
for i in range(offset, count + offset)
]
def make_job_name(
mode: str,
params: ImageParams,
size: Size,
extras: Optional[List[Optional[Param]]] = None,
) -> str:
now = int(time())
sha = sha256()
hash_value(sha, mode)
hash_value(sha, params.model)
hash_value(sha, params.pipeline)
hash_value(sha, params.scheduler)
hash_value(sha, params.prompt)
hash_value(sha, params.negative_prompt)
hash_value(sha, params.cfg)
hash_value(sha, params.seed)
hash_value(sha, params.steps)
hash_value(sha, params.eta)
hash_value(sha, params.batch)
hash_value(sha, size.width)
hash_value(sha, size.height)
if extras is not None:
for param in extras:
hash_value(sha, param)
return f"{mode}_{params.seed}_{sha.hexdigest()}_{now}"
def save_result(
server: ServerContext,
result: StageResult,
base_name: str,
save_thumbnails: bool = False,
) -> List[str]:
images = result.as_images()
result.outputs = make_output_names(server, base_name, len(images))
logger.debug("saving %s images: %s", len(images), result.outputs)
outputs = []
for image, metadata, filename in zip(images, result.metadata, result.outputs):
outputs.append(
save_image(
server,
filename,
image,
metadata,
)
)
if save_thumbnails:
result.thumbnails = make_output_names(
server,
base_name,
len(images),
suffix="thumbnail",
)
logger.debug("saving %s thumbnails: %s", len(images), result.thumbnails)
thumbnails = []
for image, filename in zip(images, result.thumbnails):
# TODO: only make a thumbnail if the image is larger than the thumbnail size
thumbnail = image.copy()
thumbnail.thumbnail((server.thumbnail_size, server.thumbnail_size))
thumbnails.append(
save_image(
server,
filename,
thumbnail,
)
)
return outputs
def save_image(
server: ServerContext,
output: str,
image: Image.Image,
metadata: Optional[ImageMetadata] = None,
) -> str:
path = base_join(server.output_path, output)
if server.image_format == "png":
exif = PngImagePlugin.PngInfo()
if metadata is not None:
exif.add_text("make", "onnx-web")
exif.add_text(
"maker note",
dumps(metadata.tojson(server, [output])),
)
exif.add_text("model", server.server_version)
exif.add_text(
"parameters",
metadata.to_exif(server),
)
image.save(path, format=server.image_format, pnginfo=exif)
else:
exif = dump(
{
"0th": {
ExifIFD.MakerNote: UserComment.dump(
dumps(metadata.tojson(server, [output])),
encoding="unicode",
),
ExifIFD.UserComment: UserComment.dump(
metadata.to_exif(server),
encoding="unicode",
),
ImageIFD.Make: "onnx-web",
ImageIFD.Model: server.server_version,
}
}
)
image.save(path, format=server.image_format, exif=exif)
if metadata is not None:
save_metadata(
server,
output,
metadata,
)
logger.debug("saved output image to: %s", path)
return path
def save_metadata(
server: ServerContext,
output: str,
metadata: ImageMetadata,
) -> str:
path = base_join(server.output_path, f"{output}.json")
json = metadata.tojson(server, [output])
with open(path, "w") as f:
f.write(dumps(json))
logger.debug("saved image params to: %s", path)
return path
def read_metadata(
image: Image.Image,
) -> Optional[ImageMetadata]:
exif_data = image.getexif()
if ImageIFD.Make in exif_data and exif_data[ImageIFD.Make] == "onnx-web":
return ImageMetadata.from_json(exif_data[ExifIFD.MakerNote])
if ExifIFD.UserComment in exif_data:
return ImageMetadata.from_exif(exif_data[ExifIFD.UserComment])
# this could return ImageMetadata.unknown_image(), but that would not indicate whether the input
# had metadata or not, so it's easier to return None and follow the call with `or ImageMetadata.unknown_image()`
return None