mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(app): generic progress events
Some processes have steps, like denoising or a tiled spandel. Denoising has its own step callback but we don't have any generic way to signal progress. Processes like a tiled spandrel run show indeterminate progress in the client. This change introduces a new event to handle this: `InvocationGenericProgressEvent` A simplified helper is added to the invocation API so nodes can easily emit progress as they do their thing.
This commit is contained in:
parent
571ba87e13
commit
487815b181
@ -22,6 +22,7 @@ from invokeai.app.services.events.events_common import (
|
|||||||
InvocationCompleteEvent,
|
InvocationCompleteEvent,
|
||||||
InvocationDenoiseProgressEvent,
|
InvocationDenoiseProgressEvent,
|
||||||
InvocationErrorEvent,
|
InvocationErrorEvent,
|
||||||
|
InvocationGenericProgressEvent,
|
||||||
InvocationStartedEvent,
|
InvocationStartedEvent,
|
||||||
ModelEventBase,
|
ModelEventBase,
|
||||||
ModelInstallCancelledEvent,
|
ModelInstallCancelledEvent,
|
||||||
@ -56,6 +57,7 @@ class BulkDownloadSubscriptionEvent(BaseModel):
|
|||||||
QUEUE_EVENTS = {
|
QUEUE_EVENTS = {
|
||||||
InvocationStartedEvent,
|
InvocationStartedEvent,
|
||||||
InvocationDenoiseProgressEvent,
|
InvocationDenoiseProgressEvent,
|
||||||
|
InvocationGenericProgressEvent,
|
||||||
InvocationCompleteEvent,
|
InvocationCompleteEvent,
|
||||||
InvocationErrorEvent,
|
InvocationErrorEvent,
|
||||||
QueueItemStatusChangedEvent,
|
QueueItemStatusChangedEvent,
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from PIL.Image import Image as PILImageType
|
||||||
|
|
||||||
from invokeai.app.services.events.events_common import (
|
from invokeai.app.services.events.events_common import (
|
||||||
BatchEnqueuedEvent,
|
BatchEnqueuedEvent,
|
||||||
BulkDownloadCompleteEvent,
|
BulkDownloadCompleteEvent,
|
||||||
@ -17,6 +19,7 @@ from invokeai.app.services.events.events_common import (
|
|||||||
InvocationCompleteEvent,
|
InvocationCompleteEvent,
|
||||||
InvocationDenoiseProgressEvent,
|
InvocationDenoiseProgressEvent,
|
||||||
InvocationErrorEvent,
|
InvocationErrorEvent,
|
||||||
|
InvocationGenericProgressEvent,
|
||||||
InvocationStartedEvent,
|
InvocationStartedEvent,
|
||||||
ModelInstallCancelledEvent,
|
ModelInstallCancelledEvent,
|
||||||
ModelInstallCompleteEvent,
|
ModelInstallCompleteEvent,
|
||||||
@ -58,6 +61,29 @@ class EventServiceBase:
|
|||||||
"""Emitted when an invocation is started"""
|
"""Emitted when an invocation is started"""
|
||||||
self.dispatch(InvocationStartedEvent.build(queue_item, invocation))
|
self.dispatch(InvocationStartedEvent.build(queue_item, invocation))
|
||||||
|
|
||||||
|
def emit_invocation_generic_progress(
|
||||||
|
self,
|
||||||
|
queue_item: "SessionQueueItem",
|
||||||
|
invocation: "BaseInvocation",
|
||||||
|
name: str,
|
||||||
|
step: int | None = None,
|
||||||
|
total_steps: int | None = None,
|
||||||
|
message: str | None = None,
|
||||||
|
image: PILImageType | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Emitted at each step during an invocation"""
|
||||||
|
self.dispatch(
|
||||||
|
InvocationGenericProgressEvent.build(
|
||||||
|
queue_item,
|
||||||
|
invocation,
|
||||||
|
name,
|
||||||
|
step,
|
||||||
|
total_steps,
|
||||||
|
message,
|
||||||
|
image,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def emit_invocation_denoise_progress(
|
def emit_invocation_denoise_progress(
|
||||||
self,
|
self,
|
||||||
queue_item: "SessionQueueItem",
|
queue_item: "SessionQueueItem",
|
||||||
|
@ -3,7 +3,8 @@ from typing import TYPE_CHECKING, Any, ClassVar, Coroutine, Generic, Optional, P
|
|||||||
|
|
||||||
from fastapi_events.handlers.local import local_handler
|
from fastapi_events.handlers.local import local_handler
|
||||||
from fastapi_events.registry.payload_schema import registry as payload_schema
|
from fastapi_events.registry.payload_schema import registry as payload_schema
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from PIL.Image import Image as PILImageType
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||||
|
|
||||||
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
||||||
from invokeai.app.services.session_queue.session_queue_common import (
|
from invokeai.app.services.session_queue.session_queue_common import (
|
||||||
@ -17,6 +18,7 @@ from invokeai.app.services.shared.graph import AnyInvocation, AnyInvocationOutpu
|
|||||||
from invokeai.app.util.misc import get_timestamp
|
from invokeai.app.util.misc import get_timestamp
|
||||||
from invokeai.backend.model_manager.config import AnyModelConfig, SubModelType
|
from invokeai.backend.model_manager.config import AnyModelConfig, SubModelType
|
||||||
from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState
|
from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState
|
||||||
|
from invokeai.backend.util.util import image_to_dataURL
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from invokeai.app.services.download.download_base import DownloadJob
|
from invokeai.app.services.download.download_base import DownloadJob
|
||||||
@ -120,6 +122,65 @@ class InvocationStartedEvent(InvocationEventBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@payload_schema.register
|
||||||
|
class InvocationGenericProgressEvent(InvocationEventBase):
|
||||||
|
"""Event model for invocation_generic_progress"""
|
||||||
|
|
||||||
|
__event_name__ = "invocation_generic_progress"
|
||||||
|
|
||||||
|
name: str = Field(description="The name of the progress type")
|
||||||
|
step: int | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="The current step. Omit for indeterminate progress.",
|
||||||
|
)
|
||||||
|
total_steps: int | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="The total number of steps. Omit for indeterminate progress.",
|
||||||
|
)
|
||||||
|
image: ProgressImage | None = Field(default=None, description="An image sent at each step during processing")
|
||||||
|
message: str | None = Field(default=None, description="A message to display with the progress")
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_step_total_steps(self):
|
||||||
|
if (self.step is None) is not (self.total_steps is None):
|
||||||
|
raise ValueError("must provide both step and total_steps or neither")
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build(
|
||||||
|
cls,
|
||||||
|
queue_item: SessionQueueItem,
|
||||||
|
invocation: AnyInvocation,
|
||||||
|
name: str,
|
||||||
|
step: int | None = None,
|
||||||
|
total_steps: int | None = None,
|
||||||
|
message: str | None = None,
|
||||||
|
image: PILImageType | None = None,
|
||||||
|
) -> "InvocationGenericProgressEvent":
|
||||||
|
image_ = (
|
||||||
|
ProgressImage(
|
||||||
|
dataURL=image_to_dataURL(image, image_format="JPEG"),
|
||||||
|
width=image.width,
|
||||||
|
height=image.height,
|
||||||
|
)
|
||||||
|
if image
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
return cls(
|
||||||
|
queue_id=queue_item.queue_id,
|
||||||
|
item_id=queue_item.item_id,
|
||||||
|
batch_id=queue_item.batch_id,
|
||||||
|
session_id=queue_item.session_id,
|
||||||
|
invocation=invocation,
|
||||||
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
|
name=name,
|
||||||
|
step=step,
|
||||||
|
total_steps=total_steps,
|
||||||
|
image=image_,
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@payload_schema.register
|
@payload_schema.register
|
||||||
class InvocationDenoiseProgressEvent(InvocationEventBase):
|
class InvocationDenoiseProgressEvent(InvocationEventBase):
|
||||||
"""Event model for invocation_denoise_progress"""
|
"""Event model for invocation_denoise_progress"""
|
||||||
|
@ -557,6 +557,50 @@ class UtilInterface(InvocationContextInterface):
|
|||||||
is_canceled=self.is_canceled,
|
is_canceled=self.is_canceled,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def signal_progress(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
step: int | None = None,
|
||||||
|
total_steps: int | None = None,
|
||||||
|
message: str | None = None,
|
||||||
|
image: Image | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Signals the progress of some long-running invocation process. The progress is displayed in the UI.
|
||||||
|
|
||||||
|
Each progress event is grouped by both the given `name` and the invocation's ID. Once the invocation completes,
|
||||||
|
future progress events with the same name will be grouped separately.
|
||||||
|
|
||||||
|
For progress that has a known number of steps, provide both `step` and `total_steps`. For indeterminate
|
||||||
|
progress, omit both `step` and `total_steps`. An error will be raised if only one of `step` and `total_steps`
|
||||||
|
is provided.
|
||||||
|
|
||||||
|
For the best user experience:
|
||||||
|
- Signal process once with `step=0, total_steps=total_steps` before processing begins.
|
||||||
|
- Signal process after each step completes with `step=current_step, total_steps=total_steps`.
|
||||||
|
- Signal process once with `step=total_steps, total_steps=total_steps` after processing completes, if this
|
||||||
|
wasn't already done.
|
||||||
|
- If the process is indeterminate, signal progress with `step=None, total_steps=None` at regular intervals.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name of the action. This is used to group progress events together.
|
||||||
|
step: The current step of the action. Omit for indeterminate progress.
|
||||||
|
total_steps: The total number of steps of the action. Omit for indeterminate progress.
|
||||||
|
message: An optional message to display. If omitted, no message will be displayed.
|
||||||
|
image: An optional image to display. If omitted, no image will be displayed.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
pydantic.ValidationError: If only one of `step` and `total_steps` is provided.
|
||||||
|
"""
|
||||||
|
self._services.events.emit_invocation_generic_progress(
|
||||||
|
queue_item=self._data.queue_item,
|
||||||
|
invocation=self._data.invocation,
|
||||||
|
name=name,
|
||||||
|
step=step,
|
||||||
|
total_steps=total_steps,
|
||||||
|
message=message,
|
||||||
|
image=image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvocationContext:
|
class InvocationContext:
|
||||||
"""Provides access to various services and data for the current invocation.
|
"""Provides access to various services and data for the current invocation.
|
||||||
|
Loading…
Reference in New Issue
Block a user