diff --git a/invokeai/app/invocations/tiles.py b/invokeai/app/invocations/tiles.py index acc87a7864..c6499c45d6 100644 --- a/invokeai/app/invocations/tiles.py +++ b/invokeai/app/invocations/tiles.py @@ -15,65 +15,65 @@ from invokeai.app.invocations.baseinvocation import ( ) from invokeai.app.invocations.primitives import ImageField, ImageOutput from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin -from invokeai.backend.tiles.tiles import calc_tiles, merge_tiles_with_linear_blending +from invokeai.backend.tiles.tiles import calc_tiles_with_overlap, merge_tiles_with_linear_blending from invokeai.backend.tiles.utils import Tile -# TODO(ryand): Is this important? -_DIMENSION_MULTIPLE_OF = 8 - class TileWithImage(BaseModel): tile: Tile image: ImageField -@invocation_output("calc_tiles_output") -class CalcTilesOutput(BaseInvocationOutput): - # TODO(ryand): Add description from FieldDescriptions. - tiles: list[Tile] = OutputField(description="") +@invocation_output("calculate_image_tiles_output") +class CalculateImageTilesOutput(BaseInvocationOutput): + tiles: list[Tile] = OutputField(description="The tiles coordinates that cover a particular image shape.") -@invocation("calculate_tiles", title="Calculate Tiles", tags=["tiles"], category="tiles", version="1.0.0") -class CalcTiles(BaseInvocation): - """TODO(ryand)""" +@invocation("calculate_image_tiles", title="Calculate Image Tiles", tags=["tiles"], category="tiles", version="1.0.0") +class CalculateImageTiles(BaseInvocation): + """Calculate the coordinates and overlaps of tiles that cover a target image shape.""" - # Inputs - image_height: int = InputField(ge=1) - image_width: int = InputField(ge=1) - tile_height: int = InputField(ge=1, multiple_of=_DIMENSION_MULTIPLE_OF, default=576) - tile_width: int = InputField(ge=1, multiple_of=_DIMENSION_MULTIPLE_OF, default=576) - overlap: int = InputField(ge=0, multiple_of=_DIMENSION_MULTIPLE_OF, default=64) + image_height: int = InputField( + ge=1, default=1024, description="The image height, in pixels, to calculate tiles for." + ) + image_width: int = InputField(ge=1, default=1024, description="The image width, in pixels, to calculate tiles for.") + tile_height: int = InputField(ge=1, default=576, description="The tile height, in pixels.") + tile_width: int = InputField(ge=1, default=576, description="The tile width, in pixels.") + overlap: int = InputField( + ge=0, + default=128, + description="The target overlap, in pixels, between adjacent tiles. Adjacent tiles will overlap by at least this amount", + ) - def invoke(self, context: InvocationContext) -> CalcTilesOutput: - tiles = calc_tiles( + def invoke(self, context: InvocationContext) -> CalculateImageTilesOutput: + tiles = calc_tiles_with_overlap( image_height=self.image_height, image_width=self.image_width, tile_height=self.tile_height, tile_width=self.tile_width, overlap=self.overlap, ) - return CalcTilesOutput(tiles=tiles) + return CalculateImageTilesOutput(tiles=tiles) @invocation_output("tile_to_properties_output") class TileToPropertiesOutput(BaseInvocationOutput): - # TODO(ryand): Add descriptions. - coords_top: int = OutputField(description="") - coords_bottom: int = OutputField(description="") - coords_left: int = OutputField(description="") - coords_right: int = OutputField(description="") + coords_top: int = OutputField(description="Top coordinate of the tile relative to its parent image.") + coords_bottom: int = OutputField(description="Bottom coordinate of the tile relative to its parent image.") + coords_left: int = OutputField(description="Left coordinate of the tile relative to its parent image.") + coords_right: int = OutputField(description="Right coordinate of the tile relative to its parent image.") - overlap_top: int = OutputField(description="") - overlap_bottom: int = OutputField(description="") - overlap_left: int = OutputField(description="") - overlap_right: int = OutputField(description="") + overlap_top: int = OutputField(description="Overlap between this tile and its top neighbor.") + overlap_bottom: int = OutputField(description="Overlap between this tile and its bottom neighbor.") + overlap_left: int = OutputField(description="Overlap between this tile and its left neighbor.") + overlap_right: int = OutputField(description="Overlap between this tile and its right neighbor.") -@invocation("tile_to_properties") +@invocation("tile_to_properties", title="Tile to Properties", tags=["tiles"], category="tiles", version="1.0.0") class TileToProperties(BaseInvocation): """Split a Tile into its individual properties.""" - tile: Tile = InputField() + tile: Tile = InputField(description="The tile to split into properties.") def invoke(self, context: InvocationContext) -> TileToPropertiesOutput: return TileToPropertiesOutput( @@ -88,19 +88,20 @@ class TileToProperties(BaseInvocation): ) -# HACK(ryand): The only reason that PairTileImage is needed is because the iterate/collect nodes don't preserve order. -# Can this be fixed? - - @invocation_output("pair_tile_image_output") class PairTileImageOutput(BaseInvocationOutput): - tile_with_image: TileWithImage = OutputField(description="") + tile_with_image: TileWithImage = OutputField(description="A tile description with its corresponding image.") @invocation("pair_tile_image", title="Pair Tile with Image", tags=["tiles"], category="tiles", version="1.0.0") class PairTileImage(BaseInvocation): - image: ImageField = InputField() - tile: Tile = InputField() + """Pair an image with its tile properties.""" + + # TODO(ryand): The only reason that PairTileImage is needed is because the iterate/collect nodes don't preserve + # order. Can this be fixed? + + image: ImageField = InputField(description="The tile image.") + tile: Tile = InputField(description="The tile properties.") def invoke(self, context: InvocationContext) -> PairTileImageOutput: return PairTileImageOutput( @@ -111,15 +112,18 @@ class PairTileImage(BaseInvocation): ) -@invocation("merge_tiles_to_image", title="Merge Tiles To Image", tags=["tiles"], category="tiles", version="1.0.0") +@invocation("merge_tiles_to_image", title="Merge Tiles to Image", tags=["tiles"], category="tiles", version="1.0.0") class MergeTilesToImage(BaseInvocation, WithMetadata, WithWorkflow): - """TODO(ryand)""" + """Merge multiple tile images into a single image.""" # Inputs - image_height: int = InputField(ge=1) - image_width: int = InputField(ge=1) - tiles_with_images: list[TileWithImage] = InputField() - blend_amount: int = InputField(ge=0) + image_height: int = InputField(ge=1, description="The height of the output image, in pixels.") + image_width: int = InputField(ge=1, description="The width of the output image, in pixels.") + tiles_with_images: list[TileWithImage] = InputField(description="A list of tile images with tile properties.") + blend_amount: int = InputField( + ge=0, + description="The amount to blend adjacent tiles in pixels. Must be <= the amount of overlap between adjacent tiles.", + ) def invoke(self, context: InvocationContext) -> ImageOutput: images = [twi.image for twi in self.tiles_with_images]