docs: update INVOCATIONS.md

This commit is contained in:
psychedelicious 2024-01-13 23:23:27 +11:00
parent 8637c40661
commit a79a450e9d

View File

@ -9,11 +9,15 @@ complex functionality.
## Invocations Directory ## Invocations Directory
InvokeAI Nodes can be found in the `invokeai/app/invocations` directory. These can be used as examples to create your own nodes. InvokeAI Nodes can be found in the `invokeai/app/invocations` directory. These
can be used as examples to create your own nodes.
New nodes should be added to a subfolder in `nodes` direction found at the root level of the InvokeAI installation location. Nodes added to this folder will be able to be used upon application startup. New nodes should be added to a subfolder in `nodes` direction found at the root
level of the InvokeAI installation location. Nodes added to this folder will be
able to be used upon application startup.
Example `nodes` subfolder structure:
Example `nodes` subfolder structure:
```py ```py
├── __init__.py # Invoke-managed custom node loader ├── __init__.py # Invoke-managed custom node loader
@ -30,14 +34,14 @@ Example `nodes` subfolder structure:
└── fancy_node.py └── fancy_node.py
``` ```
Each node folder must have an `__init__.py` file that imports its nodes. Only nodes imported in the `__init__.py` file are loaded. Each node folder must have an `__init__.py` file that imports its nodes. Only
See the README in the nodes folder for more examples: nodes imported in the `__init__.py` file are loaded. See the README in the nodes
folder for more examples:
```py ```py
from .cool_node import CoolInvocation from .cool_node import CoolInvocation
``` ```
## Creating A New Invocation ## Creating A New Invocation
In order to understand the process of creating a new Invocation, let us actually In order to understand the process of creating a new Invocation, let us actually
@ -131,7 +135,6 @@ from invokeai.app.invocations.primitives import ImageField
class ResizeInvocation(BaseInvocation): class ResizeInvocation(BaseInvocation):
'''Resizes an image''' '''Resizes an image'''
# Inputs
image: ImageField = InputField(description="The input image") image: ImageField = InputField(description="The input image")
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image") width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image") height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
@ -167,12 +170,11 @@ from invokeai.app.invocations.primitives import ImageField
class ResizeInvocation(BaseInvocation): class ResizeInvocation(BaseInvocation):
'''Resizes an image''' '''Resizes an image'''
# Inputs
image: ImageField = InputField(description="The input image") image: ImageField = InputField(description="The input image")
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image") width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image") height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
def invoke(self, context: InvocationContext): def invoke(self, context):
pass pass
``` ```
@ -197,12 +199,11 @@ from invokeai.app.invocations.image import ImageOutput
class ResizeInvocation(BaseInvocation): class ResizeInvocation(BaseInvocation):
'''Resizes an image''' '''Resizes an image'''
# Inputs
image: ImageField = InputField(description="The input image") image: ImageField = InputField(description="The input image")
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image") width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image") height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context) -> ImageOutput:
pass pass
``` ```
@ -228,31 +229,18 @@ class ResizeInvocation(BaseInvocation):
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image") width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image") height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context) -> ImageOutput:
# Load the image using InvokeAI's predefined Image Service. Returns the PIL image. # Load the input image as a PIL image
image = context.services.images.get_pil_image(self.image.image_name) image = context.images.get_pil(self.image.image_name)
# Resizing the image # Resize the image
resized_image = image.resize((self.width, self.height)) resized_image = image.resize((self.width, self.height))
# Save the image using InvokeAI's predefined Image Service. Returns the prepared PIL image. # Save the image
output_image = context.services.images.create( image_dto = context.images.save(image=resized_image)
image=resized_image,
image_origin=ResourceOrigin.INTERNAL,
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
# Returning the Image # Return an ImageOutput
return ImageOutput( return ImageOutput.build(image_dto)
image=ImageField(
image_name=output_image.image_name,
),
width=output_image.width,
height=output_image.height,
)
``` ```
**Note:** Do not be overwhelmed by the `ImageOutput` process. InvokeAI has a **Note:** Do not be overwhelmed by the `ImageOutput` process. InvokeAI has a
@ -343,27 +331,25 @@ class ImageColorStringOutput(BaseInvocationOutput):
That's all there is to it. That's all there is to it.
<!-- TODO: DANGER - we probably do not want people to create their own field types, because this requires a lot of work on the frontend to accomodate.
### Custom Input Fields ### Custom Input Fields
Now that you know how to create your own Invocations, let us dive into slightly Now that you know how to create your own Invocations, let us dive into slightly
more advanced topics. more advanced topics.
While creating your own Invocations, you might run into a scenario where the While creating your own Invocations, you might run into a scenario where the
existing input types in InvokeAI do not meet your requirements. In such cases, existing fields in InvokeAI do not meet your requirements. In such cases, you
you can create your own input types. can create your own fields.
Let us create one as an example. Let us say we want to create a color input Let us create one as an example. Let us say we want to create a color input
field that represents a color code. But before we start on that here are some field that represents a color code. But before we start on that here are some
general good practices to keep in mind. general good practices to keep in mind.
**Good Practices** ### Best Practices
- There is no naming convention for input fields but we highly recommend that - There is no naming convention for input fields but we highly recommend that
you name it something appropriate like `ColorField`. you name it something appropriate like `ColorField`.
- It is not mandatory but it is heavily recommended to add a relevant - It is not mandatory but it is heavily recommended to add a relevant
`docstring` to describe your input field. `docstring` to describe your field.
- Keep your field in the same file as the Invocation that it is made for or in - Keep your field in the same file as the Invocation that it is made for or in
another file where it is relevant. another file where it is relevant.
@ -378,10 +364,13 @@ class ColorField(BaseModel):
pass pass
``` ```
Perfect. Now let us create our custom inputs for our field. This is exactly Perfect. Now let us create the properties for our field. This is similar to how
similar how you created input fields for your Invocation. All the same rules you created input fields for your Invocation. All the same rules apply. Let us
apply. Let us create four fields representing the _red(r)_, _blue(b)_, create four fields representing the _red(r)_, _blue(b)_, _green(g)_ and
_green(g)_ and _alpha(a)_ channel of the color. _alpha(a)_ channel of the color.
> Technically, the properties are _also_ called fields - but in this case, it
> refers to a `pydantic` field.
```python ```python
class ColorField(BaseModel): class ColorField(BaseModel):
@ -396,25 +385,11 @@ That's it. We now have a new input field type that we can use in our Invocations
like this. like this.
```python ```python
color: ColorField = Field(default=ColorField(r=0, g=0, b=0, a=0), description='Background color of an image') color: ColorField = InputField(default=ColorField(r=0, g=0, b=0, a=0), description='Background color of an image')
``` ```
### Custom Components For Frontend ### Using the custom field
Every backend input type should have a corresponding frontend component so the When you start the UI, your custom field will be automatically recognized.
UI knows what to render when you use a particular field type.
If you are using existing field types, we already have components for those. So Custom fields only support connection inputs in the Workflow Editor.
you don't have to worry about creating anything new. But this might not always
be the case. Sometimes you might want to create new field types and have the
frontend UI deal with it in a different way.
This is where we venture into the world of React and Javascript and create our
own new components for our Invocations. Do not fear the world of JS. It's
actually pretty straightforward.
Let us create a new component for our custom color field we created above. When
we use a color field, let us say we want the UI to display a color picker for
the user to pick from rather than entering values. That is what we will build
now.
-->