mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
402cf9b0ee
Refactor services folder/module structure. **Motivation** While working on our services I've repeatedly encountered circular imports and a general lack of clarity regarding where to put things. The structure introduced goes a long way towards resolving those issues, setting us up for a clean structure going forward. **Services** Services are now in their own folder with a few files: - `services/{service_name}/__init__.py`: init as needed, mostly empty now - `services/{service_name}/{service_name}_base.py`: the base class for the service - `services/{service_name}/{service_name}_{impl_type}.py`: the default concrete implementation of the service - typically one of `sqlite`, `default`, or `memory` - `services/{service_name}/{service_name}_common.py`: any common items - models, exceptions, utilities, etc Though it's a bit verbose to have the service name both as the folder name and the prefix for files, I found it is _extremely_ confusing to have all of the base classes just be named `base.py`. So, at the cost of some verbosity when importing things, I've included the service name in the filename. There are some minor logic changes. For example, in `InvocationProcessor`, instead of assigning the model manager service to a variable to be used later in the file, the service is used directly via the `Invoker`. **Shared** Things that are used across disparate services are in `services/shared/`: - `default_graphs.py`: previously in `services/` - `graphs.py`: previously in `services/` - `paginatation`: generic pagination models used in a few services - `sqlite`: the `SqliteDatabase` class, other sqlite-specific things
123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
import torch
|
|
from PIL import Image
|
|
|
|
from invokeai.app.services.invocation_processor.invocation_processor_common import CanceledException, ProgressImage
|
|
|
|
from ...backend.model_management.models import BaseModelType
|
|
from ...backend.stable_diffusion import PipelineIntermediateState
|
|
from ...backend.util.util import image_to_dataURL
|
|
from ..invocations.baseinvocation import InvocationContext
|
|
|
|
|
|
def sample_to_lowres_estimated_image(samples, latent_rgb_factors, smooth_matrix=None):
|
|
latent_image = samples[0].permute(1, 2, 0) @ latent_rgb_factors
|
|
|
|
if smooth_matrix is not None:
|
|
latent_image = latent_image.unsqueeze(0).permute(3, 0, 1, 2)
|
|
latent_image = torch.nn.functional.conv2d(latent_image, smooth_matrix.reshape((1, 1, 3, 3)), padding=1)
|
|
latent_image = latent_image.permute(1, 2, 3, 0).squeeze(0)
|
|
|
|
latents_ubyte = (
|
|
((latent_image + 1) / 2).clamp(0, 1).mul(0xFF).byte() # change scale from -1..1 to 0..1 # to 0..255
|
|
).cpu()
|
|
|
|
return Image.fromarray(latents_ubyte.numpy())
|
|
|
|
|
|
def stable_diffusion_step_callback(
|
|
context: InvocationContext,
|
|
intermediate_state: PipelineIntermediateState,
|
|
node: dict,
|
|
source_node_id: str,
|
|
base_model: BaseModelType,
|
|
):
|
|
if context.services.queue.is_canceled(context.graph_execution_state_id):
|
|
raise CanceledException
|
|
|
|
# Some schedulers report not only the noisy latents at the current timestep,
|
|
# but also their estimate so far of what the de-noised latents will be. Use
|
|
# that estimate if it is available.
|
|
if intermediate_state.predicted_original is not None:
|
|
sample = intermediate_state.predicted_original
|
|
else:
|
|
sample = intermediate_state.latents
|
|
|
|
# TODO: This does not seem to be needed any more?
|
|
# # txt2img provides a Tensor in the step_callback
|
|
# # img2img provides a PipelineIntermediateState
|
|
# if isinstance(sample, PipelineIntermediateState):
|
|
# # this was an img2img
|
|
# print('img2img')
|
|
# latents = sample.latents
|
|
# step = sample.step
|
|
# else:
|
|
# print('txt2img')
|
|
# latents = sample
|
|
# step = intermediate_state.step
|
|
|
|
# TODO: only output a preview image when requested
|
|
|
|
if base_model in [BaseModelType.StableDiffusionXL, BaseModelType.StableDiffusionXLRefiner]:
|
|
# fast latents preview matrix for sdxl
|
|
# generated by @StAlKeR7779
|
|
sdxl_latent_rgb_factors = torch.tensor(
|
|
[
|
|
# R G B
|
|
[0.3816, 0.4930, 0.5320],
|
|
[-0.3753, 0.1631, 0.1739],
|
|
[0.1770, 0.3588, -0.2048],
|
|
[-0.4350, -0.2644, -0.4289],
|
|
],
|
|
dtype=sample.dtype,
|
|
device=sample.device,
|
|
)
|
|
|
|
sdxl_smooth_matrix = torch.tensor(
|
|
[
|
|
[0.0358, 0.0964, 0.0358],
|
|
[0.0964, 0.4711, 0.0964],
|
|
[0.0358, 0.0964, 0.0358],
|
|
],
|
|
dtype=sample.dtype,
|
|
device=sample.device,
|
|
)
|
|
|
|
image = sample_to_lowres_estimated_image(sample, sdxl_latent_rgb_factors, sdxl_smooth_matrix)
|
|
else:
|
|
# origingally adapted from code by @erucipe and @keturn here:
|
|
# https://discuss.huggingface.co/t/decoding-latents-to-rgb-without-upscaling/23204/7
|
|
|
|
# these updated numbers for v1.5 are from @torridgristle
|
|
v1_5_latent_rgb_factors = torch.tensor(
|
|
[
|
|
# R G B
|
|
[0.3444, 0.1385, 0.0670], # L1
|
|
[0.1247, 0.4027, 0.1494], # L2
|
|
[-0.3192, 0.2513, 0.2103], # L3
|
|
[-0.1307, -0.1874, -0.7445], # L4
|
|
],
|
|
dtype=sample.dtype,
|
|
device=sample.device,
|
|
)
|
|
|
|
image = sample_to_lowres_estimated_image(sample, v1_5_latent_rgb_factors)
|
|
|
|
(width, height) = image.size
|
|
width *= 8
|
|
height *= 8
|
|
|
|
dataURL = image_to_dataURL(image, image_format="JPEG")
|
|
|
|
context.services.events.emit_generator_progress(
|
|
queue_id=context.queue_id,
|
|
queue_item_id=context.queue_item_id,
|
|
queue_batch_id=context.queue_batch_id,
|
|
graph_execution_state_id=context.graph_execution_state_id,
|
|
node=node,
|
|
source_node_id=source_node_id,
|
|
progress_image=ProgressImage(width=width, height=height, dataURL=dataURL),
|
|
step=intermediate_state.step,
|
|
order=intermediate_state.order,
|
|
total_steps=intermediate_state.total_steps,
|
|
)
|