Files

169 lines
5.0 KiB
Python

import torch
class PermuteMaskBatch:
# no internal state
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
"""
Input: mask
"""
return {
"required": {
"masks": ("MASK",)
},
}
RETURN_TYPES = ("MASK",)
RETURN_NAMES = ("MASK",)
FUNCTION = "permuteMaskBatch"
OUTPUT_NODE = False
CATEGORY = "mask"
def permuteMaskBatch(self, masks):
n, h, w = masks.shape
combinations = 2 ** n
output = torch.zeros((combinations, h, w), dtype=masks.dtype)
for i in range(1, combinations):
# exploits the fact that the index is itself a bit pattern 00, 01, 10, 11, etc.
included = [j for j in range(n) if (i & (1 << j))]
if included:
combined = torch.stack([masks[j] for j in included]).max(dim=0)[0]
output[i] = combined
return (output,)
class FlattenAgainstOriginal:
# no internal state
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"base_image": ("IMAGE",),
"candidates": ("IMAGE",),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("IMAGE",)
OUTPUT_IS_LIST = (False,)
FUNCTION = "flattenAgainstOriginal"
OUTPUT_NODE = False
CATEGORY = "image"
def flattenAgainstOriginal(self, base_image, candidates):
print('inbound shape', base_image.shape)
target = base_image.clone()
for b_idx, b in enumerate(target):
if b.shape[-1] == 4:
b_rgb = b[..., :3]
b_alp = b[..., -1:]
elif b.shape[-1] == 3:
b_rgb = b
b_alp = torch.ones(
b.shape[:-1] + (1,),
dtype = b.dtype,
device = b.device
)
else:
raise ValueError('final dimension of base images must be 3 or 4')
for c_idx, c in enumerate(candidates):
c_rgb = c[..., :3]
c_alp = c[..., -1:]
new_a = c_alp + (b_alp * (1 - c_alp))
mask_area = (new_a > 0).squeeze(-1)
new_rgb = torch.zeros_like(b_rgb)
new_rgb[mask_area] = (
c_rgb[mask_area] * c_alp[mask_area] +
b_rgb[mask_area] * b_alp[mask_area] * (1 - c_alp[mask_area])
) / new_a[mask_area]
b_rgb = new_rgb
b_alp = new_a
if target.shape[-1] == 4:
target[b_idx] = torch.cat((b_rgb, b_alp), dim = -1)
else:
target[b_idx] = b_rgb
print('return shape', target.shape)
return (target,)
class CombinatorialDetailer:
# no internal state
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
"""
Input: mask
"""
return {
"required": {
"masks": ("MASK",),
"base_image": ("IMAGE",),
"candidates": ("IMAGE",),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("IMAGE",)
FUNCTION = "combinatorialDetailer"
OUTPUT_NODE = False
CATEGORY = "image"
def combinatorialDetailer(self, masks, base_image, candidates):
candidate_count, height, width, _ = candidates.shape
mask_count = masks.shape[0]
expanded_masks = [x.unsqueeze(-1) for x in masks]
# Each mask area can be in one of `n + 1` states (all candidates + base)
num_combinations = (candidate_count + 1) ** mask_count
output_images = torch.zeros((num_combinations, height, width, 3), dtype=base_image.dtype)
output_images[0] = base_image[0]
# Iterate over all other possible combinations
for i in range(1, num_combinations):
combined_image = base_image[0].clone()
current_combination = i
for mask_index in range(mask_count):
selected_candidate = current_combination % (candidate_count + 1)
# print("out image", i, "mask index", mask_index, "selected candidate", selected_candidate)
if selected_candidate != 0:
combined_image = torch.where(expanded_masks[mask_index] == 1, candidates[selected_candidate - 1], combined_image)
current_combination //= (candidate_count + 1)
output_images[i] = combined_image
return (output_images,)
NODE_CLASS_MAPPINGS= {
"PermuteMaskBatch": PermuteMaskBatch,
"CombinatorialDetailer": CombinatorialDetailer,
"FlattenAgainstOriginal": FlattenAgainstOriginal,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PermuteMaskBatch": "Permute Mask Batch",
"CombinatorialDetailer": "Combinatorial Detailer",
"FlattenAgainstOriginal": "Flatten Batch against Original",
}
__version__ = "1.1.0"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]