327 lines
11 KiB
Python
327 lines
11 KiB
Python
from .imgio.converter import PILHandlingHodes
|
|
from .autonode import node_wrapper, get_node_names_mappings, validate, anytype, PILImage
|
|
from .utils.tagger import get_tags, tagger_keys
|
|
from PIL import Image, ImageFilter
|
|
|
|
auxilary_classes = []
|
|
auxilary_node = node_wrapper(auxilary_classes)
|
|
|
|
@auxilary_node
|
|
class GetRatingNode:
|
|
FUNCTION = "get_rating_class"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get Rating Class"
|
|
@staticmethod
|
|
def get_rating_class(image, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result_dict = get_tags(image, model_name=model_name)
|
|
return (result_dict['rating'], )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
|
|
@auxilary_node
|
|
class GetRatingFromTextNode:
|
|
FUNCTION = "get_rating_class"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get Rating Class From Text"
|
|
@staticmethod
|
|
def get_rating_class(image, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result_dict = get_tags(image, model_name=model_name)
|
|
return (result_dict['rating'], )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("STRING", {"default": "/path/to/image.jpg"}),
|
|
},
|
|
"optional": {
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
|
|
def pixelate(image, pixelation_factor=0.1):
|
|
# Downscale the image
|
|
small = image.resize(
|
|
(int(image.width * pixelation_factor), int(image.height * pixelation_factor)),
|
|
resample=Image.NEAREST
|
|
)
|
|
# Upscale back to original size
|
|
return small.resize(image.size, Image.NEAREST)
|
|
|
|
|
|
def pixelate_target_tiles(image, max_tiles=100):
|
|
aspect_ratio = image.width / image.height
|
|
# solve h * a * h = max_tiles
|
|
h = int((max_tiles / aspect_ratio) ** 0.5)
|
|
w = int(aspect_ratio * h)
|
|
h, w = max(4, h), max(4, w)
|
|
# Downscale the image
|
|
small = image.resize(
|
|
(w, h),
|
|
resample=Image.NEAREST,
|
|
)
|
|
# Upscale back to original size
|
|
return small.resize(image.size, Image.NEAREST)
|
|
|
|
|
|
@auxilary_node
|
|
class CensorImageByRating:
|
|
FUNCTION = "censor_image"
|
|
RETURN_TYPES = ("IMAGE",)
|
|
CATEGORY = "image"
|
|
custom_name = "Censor Image by Rating"
|
|
|
|
@staticmethod
|
|
@PILHandlingHodes.output_wrapper
|
|
def censor_image(image, rating_threshold, censor_method, model_name=None):
|
|
# Convert input to a PIL image
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result_dict = get_tags(image, model_name=model_name)
|
|
rating = result_dict['rating']
|
|
# If rating is general, no censorship required
|
|
if rating.lower() == "general":
|
|
return (image,)
|
|
censor_image = False
|
|
if rating_threshold == "general":
|
|
# censor if not general
|
|
if rating.lower() != "general":
|
|
censor_image = True
|
|
elif rating_threshold == "sensitive":
|
|
# censor if not general or sensitive
|
|
if rating.lower() not in ["general", "sensitive"]:
|
|
censor_image = True
|
|
elif rating_threshold == "questionable":
|
|
# censor if not general, sensitive or questionable
|
|
if rating.lower() not in ["general", "sensitive", "questionable"]:
|
|
censor_image = True
|
|
elif rating_threshold == "explicit":
|
|
return (image,) # why are you using this?
|
|
if censor_image:
|
|
if censor_method.lower() == "white":
|
|
# Return a white image of the same size
|
|
censored_image = Image.new("RGB", image.size, (255, 255, 255))
|
|
return (censored_image,)
|
|
|
|
elif censor_method.lower() == "blur":
|
|
# Apply a strong blur (you can adjust radius as needed)
|
|
censored_image = image.filter(ImageFilter.GaussianBlur(radius=40))
|
|
return (censored_image,)
|
|
elif censor_method.lower() == "pixelate":
|
|
# first, blur
|
|
censored_image = image.filter(ImageFilter.GaussianBlur(radius=20))
|
|
censored_image = pixelate_target_tiles(censored_image, max_tiles=100)
|
|
return (censored_image,)
|
|
|
|
# If unknown method is provided, just return the original image
|
|
return (image,)
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"rating_threshold": (["general", "sensitive", "questionable", "explicit"],),
|
|
"censor_method": (["blur", "white","pixelate"],),
|
|
},
|
|
"optional": {
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
|
|
@auxilary_node
|
|
class FilterTagsNode:
|
|
"""
|
|
Filters tags, given a list of tags splitted by ",".
|
|
We assume the input text is splittable by "," (or separator). Then, if any tags contain the filter, we remove matching tags.
|
|
"""
|
|
FUNCTION = "filter_tags"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "safety"
|
|
custom_name = "Filter Tags"
|
|
@staticmethod
|
|
def filter_tags(tags, filter_tags, separator):
|
|
filter_tags = filter_tags.split(",")
|
|
filter_tags = [tag.strip() for tag in filter_tags]
|
|
tags = tags.split(separator)
|
|
tags = [tag.strip() for tag in tags]
|
|
filtered = []
|
|
for tag in tags:
|
|
if all(filter_tag not in tag for filter_tag in filter_tags):
|
|
filtered.append(tag)
|
|
return (separator.join(filtered), )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"tags": ("STRING",),
|
|
"filter_tags": ("STRING",),
|
|
},
|
|
# optional separator
|
|
"optional": {
|
|
"separator": ("STRING", {"default": ","}),
|
|
}
|
|
}
|
|
|
|
@auxilary_node
|
|
class GetTagsAboveThresholdNode:
|
|
FUNCTION = "get_tags_above_threshold"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get Tags Above Threshold"
|
|
@staticmethod
|
|
def get_tags_above_threshold(image, threshold, replace, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
|
|
return (", ".join(result_dict['tags']), )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"threshold": ("FLOAT", {"default": 0.4}),
|
|
"replace": ("BOOLEAN", {"default": False}),
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
|
|
@auxilary_node
|
|
class GetTagsAboveThresholdFromTextNode:
|
|
FUNCTION = "get_tags_above_threshold"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get Tags Above Threshold From Text"
|
|
@staticmethod
|
|
def get_tags_above_threshold(image, threshold, replace, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
|
|
return (", ".join(result_dict['tags']), )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"threshold": ("FLOAT", {"default": 0.4}),
|
|
"replace": ("BOOLEAN", {"default": False}),
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
|
|
@auxilary_node
|
|
class GetCharactersAboveThresholdNode:
|
|
FUNCTION = "get_tags_above_threshold"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get Chars Above Threshold"
|
|
@staticmethod
|
|
def get_tags_above_threshold(image, threshold, replace, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
|
|
return (", ".join(result_dict['chars']), )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"threshold": ("FLOAT", {"default": 0.4}),
|
|
"replace": ("BOOLEAN", {"default": False}),
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
|
|
@auxilary_node
|
|
class GetCharactersAboveThresholdFromTextNode:
|
|
FUNCTION = "get_tags_above_threshold"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get Chars Above Threshold From Text"
|
|
@staticmethod
|
|
def get_tags_above_threshold(image, threshold, replace, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
|
|
return (", ".join(result_dict['chars']), )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"threshold": ("FLOAT", {"default": 0.4}),
|
|
"replace": ("BOOLEAN", {"default": False}),
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
|
|
@auxilary_node
|
|
class GetAllTagsAboveThresholdNode:
|
|
FUNCTION = "get_tags"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get All Tags Above Threshold"
|
|
@staticmethod
|
|
def get_tags(image, threshold, replace, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
|
|
result_list = []
|
|
result_list.append(result['rating'])
|
|
result_list.extend(result['tags'])
|
|
result_list.extend(result['chars'])
|
|
return (", ".join(result_list), )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"threshold": ("FLOAT", {"default": 0.4}),
|
|
"replace": ("BOOLEAN", {"default": False}),
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
@auxilary_node
|
|
class GetAllTagsExceptCharacterAboveThresholdNode:
|
|
FUNCTION = "get_tags"
|
|
RETURN_TYPES = ("STRING",)
|
|
CATEGORY = "tagger"
|
|
custom_name = "Get All Tags Above Threshold Except Characters"
|
|
@staticmethod
|
|
def get_tags(image, threshold, replace, model_name):
|
|
image = PILHandlingHodes.handle_input(image)
|
|
result = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
|
|
result_list = []
|
|
result_list.append(result['rating'])
|
|
result_list.extend(result['tags'])
|
|
return (", ".join(result_list), )
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"threshold": ("FLOAT", {"default": 0.4}),
|
|
"replace": ("BOOLEAN", {"default": False}),
|
|
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
|
|
}
|
|
}
|
|
CLASS_MAPPINGS, CLASS_NAMES = get_node_names_mappings(auxilary_classes)
|
|
validate(auxilary_classes)
|