2024-04-09 10:27:03 +00:00
|
|
|
import numpy as np
|
2024-03-08 15:30:55 +00:00
|
|
|
import torch
|
2024-07-31 21:15:48 +00:00
|
|
|
from PIL import Image
|
2024-03-08 15:30:55 +00:00
|
|
|
|
2024-04-19 13:00:44 +00:00
|
|
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, Classification, InvocationContext, invocation
|
2024-07-31 21:15:48 +00:00
|
|
|
from invokeai.app.invocations.fields import ImageField, InputField, TensorField, WithBoard, WithMetadata
|
|
|
|
from invokeai.app.invocations.primitives import ImageOutput, MaskOutput
|
2024-03-08 15:30:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
@invocation(
|
|
|
|
"rectangle_mask",
|
|
|
|
title="Create Rectangle Mask",
|
|
|
|
tags=["conditioning"],
|
|
|
|
category="conditioning",
|
2024-04-09 19:17:55 +00:00
|
|
|
version="1.0.1",
|
2024-03-08 15:30:55 +00:00
|
|
|
)
|
|
|
|
class RectangleMaskInvocation(BaseInvocation, WithMetadata):
|
|
|
|
"""Create a rectangular mask."""
|
|
|
|
|
|
|
|
width: int = InputField(description="The width of the entire mask.")
|
2024-04-09 19:17:55 +00:00
|
|
|
height: int = InputField(description="The height of the entire mask.")
|
2024-03-08 15:30:55 +00:00
|
|
|
x_left: int = InputField(description="The left x-coordinate of the rectangular masked region (inclusive).")
|
2024-04-09 19:17:55 +00:00
|
|
|
y_top: int = InputField(description="The top y-coordinate of the rectangular masked region (inclusive).")
|
2024-03-08 15:30:55 +00:00
|
|
|
rectangle_width: int = InputField(description="The width of the rectangular masked region.")
|
2024-04-09 19:17:55 +00:00
|
|
|
rectangle_height: int = InputField(description="The height of the rectangular masked region.")
|
2024-03-08 15:30:55 +00:00
|
|
|
|
|
|
|
def invoke(self, context: InvocationContext) -> MaskOutput:
|
|
|
|
mask = torch.zeros((1, self.height, self.width), dtype=torch.bool)
|
2024-05-29 23:46:46 +00:00
|
|
|
mask[:, self.y_top : self.y_top + self.rectangle_height, self.x_left : self.x_left + self.rectangle_width] = (
|
|
|
|
True
|
|
|
|
)
|
2024-03-08 15:30:55 +00:00
|
|
|
|
2024-04-08 18:16:22 +00:00
|
|
|
mask_tensor_name = context.tensors.save(mask)
|
2024-03-08 15:30:55 +00:00
|
|
|
return MaskOutput(
|
2024-04-08 18:16:22 +00:00
|
|
|
mask=TensorField(tensor_name=mask_tensor_name),
|
2024-03-08 15:30:55 +00:00
|
|
|
width=self.width,
|
|
|
|
height=self.height,
|
|
|
|
)
|
2024-04-09 10:27:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
@invocation(
|
|
|
|
"alpha_mask_to_tensor",
|
|
|
|
title="Alpha Mask to Tensor",
|
|
|
|
tags=["conditioning"],
|
|
|
|
category="conditioning",
|
|
|
|
version="1.0.0",
|
2024-04-19 13:00:44 +00:00
|
|
|
classification=Classification.Beta,
|
2024-04-09 10:27:03 +00:00
|
|
|
)
|
|
|
|
class AlphaMaskToTensorInvocation(BaseInvocation):
|
|
|
|
"""Convert a mask image to a tensor. Opaque regions are 1 and transparent regions are 0."""
|
|
|
|
|
|
|
|
image: ImageField = InputField(description="The mask image to convert.")
|
2024-04-15 02:37:30 +00:00
|
|
|
invert: bool = InputField(default=False, description="Whether to invert the mask.")
|
2024-04-09 10:27:03 +00:00
|
|
|
|
|
|
|
def invoke(self, context: InvocationContext) -> MaskOutput:
|
|
|
|
image = context.images.get_pil(self.image.image_name)
|
|
|
|
mask = torch.zeros((1, image.height, image.width), dtype=torch.bool)
|
2024-04-15 02:37:30 +00:00
|
|
|
if self.invert:
|
|
|
|
mask[0] = torch.tensor(np.array(image)[:, :, 3] == 0, dtype=torch.bool)
|
|
|
|
else:
|
|
|
|
mask[0] = torch.tensor(np.array(image)[:, :, 3] > 0, dtype=torch.bool)
|
2024-04-09 10:27:03 +00:00
|
|
|
|
|
|
|
return MaskOutput(
|
2024-04-19 03:54:38 +00:00
|
|
|
mask=TensorField(tensor_name=context.tensors.save(mask)),
|
|
|
|
height=mask.shape[1],
|
|
|
|
width=mask.shape[2],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@invocation(
|
|
|
|
"invert_tensor_mask",
|
|
|
|
title="Invert Tensor Mask",
|
|
|
|
tags=["conditioning"],
|
|
|
|
category="conditioning",
|
|
|
|
version="1.0.0",
|
2024-04-19 13:00:44 +00:00
|
|
|
classification=Classification.Beta,
|
2024-04-19 03:54:38 +00:00
|
|
|
)
|
|
|
|
class InvertTensorMaskInvocation(BaseInvocation):
|
|
|
|
"""Inverts a tensor mask."""
|
|
|
|
|
|
|
|
mask: TensorField = InputField(description="The tensor mask to convert.")
|
|
|
|
|
|
|
|
def invoke(self, context: InvocationContext) -> MaskOutput:
|
|
|
|
mask = context.tensors.load(self.mask.tensor_name)
|
|
|
|
inverted = ~mask
|
|
|
|
|
|
|
|
return MaskOutput(
|
|
|
|
mask=TensorField(tensor_name=context.tensors.save(inverted)),
|
|
|
|
height=inverted.shape[1],
|
|
|
|
width=inverted.shape[2],
|
2024-04-09 10:27:03 +00:00
|
|
|
)
|
2024-04-20 11:58:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
@invocation(
|
|
|
|
"image_mask_to_tensor",
|
|
|
|
title="Image Mask to Tensor",
|
|
|
|
tags=["conditioning"],
|
|
|
|
category="conditioning",
|
|
|
|
version="1.0.0",
|
|
|
|
)
|
|
|
|
class ImageMaskToTensorInvocation(BaseInvocation, WithMetadata):
|
|
|
|
"""Convert a mask image to a tensor. Converts the image to grayscale and uses thresholding at the specified value."""
|
|
|
|
|
|
|
|
image: ImageField = InputField(description="The mask image to convert.")
|
|
|
|
cutoff: int = InputField(ge=0, le=255, description="Cutoff (<)", default=128)
|
|
|
|
invert: bool = InputField(default=False, description="Whether to invert the mask.")
|
|
|
|
|
|
|
|
def invoke(self, context: InvocationContext) -> MaskOutput:
|
|
|
|
image = context.images.get_pil(self.image.image_name, mode="L")
|
|
|
|
|
|
|
|
mask = torch.zeros((1, image.height, image.width), dtype=torch.bool)
|
|
|
|
if self.invert:
|
|
|
|
mask[0] = torch.tensor(np.array(image)[:, :] >= self.cutoff, dtype=torch.bool)
|
|
|
|
else:
|
|
|
|
mask[0] = torch.tensor(np.array(image)[:, :] < self.cutoff, dtype=torch.bool)
|
|
|
|
|
|
|
|
return MaskOutput(
|
|
|
|
mask=TensorField(tensor_name=context.tensors.save(mask)),
|
|
|
|
height=mask.shape[1],
|
|
|
|
width=mask.shape[2],
|
|
|
|
)
|
2024-07-31 21:15:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
@invocation(
|
|
|
|
"tensor_mask_to_image",
|
|
|
|
title="Tensor Mask to Image",
|
|
|
|
tags=["mask"],
|
|
|
|
category="mask",
|
|
|
|
version="1.0.0",
|
|
|
|
)
|
|
|
|
class MaskTensorToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|
|
|
"""Convert a mask tensor to an image."""
|
|
|
|
|
|
|
|
mask: TensorField = InputField(description="The mask tensor to convert.")
|
|
|
|
|
|
|
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
|
|
|
mask = context.tensors.load(self.mask.tensor_name)
|
|
|
|
# Ensure that the mask is binary.
|
|
|
|
if mask.dtype != torch.bool:
|
|
|
|
mask = mask > 0.5
|
2024-08-01 14:12:24 +00:00
|
|
|
mask_np = (mask.float() * 255).byte().cpu().numpy()
|
2024-07-31 21:15:48 +00:00
|
|
|
|
|
|
|
mask_pil = Image.fromarray(mask_np, mode="L")
|
|
|
|
image_dto = context.images.save(image=mask_pil)
|
|
|
|
return ImageOutput.build(image_dto)
|