init repo
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
.tox/
|
||||
.venv/
|
||||
.vscode/
|
||||
.vscode-test/
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
import torch
|
||||
|
||||
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# PIL to Tensor
|
||||
|
||||
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
def crop_ndarray4(npimg, crop_region):
|
||||
x1 = crop_region[0]
|
||||
y1 = crop_region[1]
|
||||
x2 = crop_region[2]
|
||||
y2 = crop_region[3]
|
||||
|
||||
cropped = npimg[:, y1:y2, x1:x2, :]
|
||||
|
||||
return cropped
|
||||
|
||||
|
||||
def crop_image(image, crop_region):
|
||||
return crop_ndarray4(np.array(image), crop_region)
|
||||
|
||||
|
||||
def empty_pil_tensor(w=64, h=64):
|
||||
image = Image.new("RGB", (w, h))
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.rectangle((0, 0, w-1, h-1), fill=(0, 0, 0))
|
||||
return pil2tensor(image)
|
||||
|
||||
|
||||
class OFFCenterCrop:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
Return a dictionary which contains config for all input fields.
|
||||
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
|
||||
Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
|
||||
The type can be a list for selection.
|
||||
|
||||
Returns: `dict`:
|
||||
- Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required`
|
||||
- Value input_fields (`dict`): Contains input fields config:
|
||||
* Key field_name (`string`): Name of a entry-point method's argument
|
||||
* Value field_config (`tuple`):
|
||||
+ First value is a string indicate the type of field or a list for selection.
|
||||
+ Secound value is a config for type "INT", "STRING" or "FLOAT".
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",)
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
# RETURN_NAMES = ("image_output_name",)
|
||||
|
||||
FUNCTION = "image_crop"
|
||||
|
||||
# OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = "OFF"
|
||||
|
||||
# Tensor to PIL
|
||||
|
||||
def image_crop(self, image):
|
||||
image = tensor2pil(image)
|
||||
img_width, img_height = image.size
|
||||
|
||||
crop_size = img_width
|
||||
if img_width > img_height:
|
||||
crop_size = img_height
|
||||
|
||||
top = (img_height - crop_size)/2
|
||||
left = (img_width - crop_size)/2
|
||||
bottom = top + crop_size
|
||||
right = left + crop_size
|
||||
|
||||
# Calculate the final coordinates for cropping
|
||||
crop_top = max(top, 0)
|
||||
crop_left = max(left, 0)
|
||||
crop_bottom = min(bottom, img_height)
|
||||
crop_right = min(right, img_width)
|
||||
|
||||
# Ensure that the cropping region has non-zero width and height
|
||||
crop_width = crop_right - crop_left
|
||||
crop_height = crop_bottom - crop_top
|
||||
if crop_width <= 0 or crop_height <= 0:
|
||||
raise ValueError(
|
||||
"Invalid crop dimensions. Please check the values for top, left, right, and bottom.")
|
||||
|
||||
# Crop the image and resize
|
||||
crop = image.crop((crop_left, crop_top, crop_right, crop_bottom))
|
||||
|
||||
crop = crop.resize(
|
||||
(((crop.size[0] // 8) * 8), ((crop.size[1] // 8) * 8)))
|
||||
|
||||
return (pil2tensor(crop),)
|
||||
|
||||
|
||||
class OFFSEGSToImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
},
|
||||
"optional": {
|
||||
"fallback_image_opt": ("IMAGE", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "OFF"
|
||||
|
||||
def doit(self, segs, fallback_image_opt=None):
|
||||
results = list()
|
||||
|
||||
for seg in segs[1]:
|
||||
if seg.cropped_image is not None:
|
||||
cropped_image = torch.from_numpy(seg.cropped_image)
|
||||
elif fallback_image_opt is not None:
|
||||
# take from original image
|
||||
cropped_image = torch.from_numpy(
|
||||
crop_image(fallback_image_opt, seg.crop_region))
|
||||
else:
|
||||
cropped_image = empty_pil_tensor()
|
||||
|
||||
results.append(cropped_image)
|
||||
|
||||
if len(results) == 0:
|
||||
results.append(empty_pil_tensor())
|
||||
|
||||
return (results[0],)
|
||||
|
||||
class GWNumFormatter:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"input_number": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0, # Minimum value
|
||||
"max": 100000000, # Maximum value
|
||||
}),
|
||||
"width": ("INT", {
|
||||
"default": 3,
|
||||
"min": 0,
|
||||
"max": 10,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
# RETURN_NAMES = ("image_output_name",)
|
||||
|
||||
FUNCTION = "format"
|
||||
|
||||
# OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = "GW"
|
||||
|
||||
def format(self, input_number, width):
|
||||
return (f"%0{width}d" % (input_number),)
|
||||
|
||||
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
# NOTE: names should be globally unique
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Image Crop Fit": OFFCenterCrop,
|
||||
"OFF SEGS to Image": OFFSEGSToImage,
|
||||
"GW Number Formatting": GWNumFormatter
|
||||
}
|
||||
|
||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Image Crop Fit": "Image Crop Fit Node",
|
||||
"OFF SEGS to Image": "OFF SEGS to Image",
|
||||
"GW Number Formatting": "GW Number Formatting Node"
|
||||
}
|
||||
Reference in New Issue
Block a user