diff --git a/README.md b/README.md index 309fc2b..df6d5c5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 转成base64的节点都是输出节点,websocket消息中会包含base64Images和base64Type属性(具体格式请查看ImageNode.py中的ImageToBase64Advanced类源代码,或者自己搭建简单流程运行在浏览器开发者工具-->网络中查看) -Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带宽可能也会有瓶颈,条件允许可以把图片上传到OSS服务器得到URL,然后用LoadImageFromUrl加载,由于无相关OSS账号,上传OSS节点需自行编写,暂不支持。 +Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带宽可能也会有瓶颈,条件允许可以把图片上传到OSS服务器得到URL,然后用LoadImageFromURL加载,由于无相关OSS账号,上传OSS节点需自行编写,暂不支持。 ## 安装 - 方式1:通过ComfyUI-Manager安装 @@ -22,62 +22,67 @@ Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带 ``` ## 节点 -| 名称 | 说明 | -|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| LoadImageFromURL | 从网络地址加载图片,一行代表一个图片 | -| LoadMaskFromURL | 从网络地址加载遮罩,一行代表一个 | -| Base64ToImage | 把图片base64字符串转成图片 | -| Base64ToMask | 把遮罩图片base64字符串转成遮罩 | -| ImageToBase64Advanced | 把图片转成base64字符串, 可以选择图片类型(image, mask) ,方便接口调用判断 | -| ImageToBase64 | 把图片转成base64字符串(imageType=["image"]) | -| MaskToBase64Image | 把遮罩转成对应图片的base64字符串(imageType=["mask"]) | -| MaskImageToBase64 | 把遮罩图片转成base64字符串(imageType=["mask"]) | -| LoadImageToBase64 | 加载本地图片转成base64字符串 | -| SamAutoMaskSEGS | 得到图片所有语义分割的coco或uncompress_rle格式。
配合ComfyUI-Impact-Pack的SAMLoader或comfyui_segment_anything的SAMModelLoader。
但是如果使用hq模型,必须使用comfyui_segment_anything | -| InsightFaceBBOXDetect | 为图片中的人脸添加序号和区域框 | -| ColorPicker | 颜色选择器 | -| IntToNumber | 整型转数字 | -| StringToList | 字符串转列表 | -| IntToList | 整型转列表 | -| ListMerge | 列表合并 | -| JoinList | 列表根据指定分隔符连接 | -| ShowString | 显示字符串(可指定消息中key值) | -| ShowInt | 显示整型(可指定消息中key值) | -| ShowFloat | 显示浮点型(可指定消息中key值) | -| ShowNumber | 显示数字(可指定消息中key值) | -| ShowBoolean | 显示布尔值(可指定消息中key值) | -| ImageEqual | 图片是否相等(可用于通过判断遮罩图是否全黑来判定是否有遮罩) | -| SDBaseVerNumber | 判断SD大模型版本是1.5还是xl | -| ListWrapper | 包装成列表(任意类型) | -| ListUnWrapper | 转成输出列表,后面连接的节点会把每个元素执行一遍,实现类似遍历效果 | -| BboxToCropData | bbox转cropData,方便接入was节点使用 | -| BboxToBbox | bbox两种格式(x,y,w,h)和(x1,y1,x2,y2)的相互转换 | -| BboxesToBboxes | BboxToBbox节点的列表版本 | -| SelectBbox | 从Bbox列表中选择一个 | -| SelectBboxes | 从Bbox列表中选择多个 | -| CropImageByBbox | 根据Bbox区域裁剪图片 | -| MaskByBboxes | 根据Bbox列表画遮罩 | -| SplitStringToList | 根据分隔符把字符串拆分为某种数据类型(str/int/float/bool)的列表 | -| IndexOfList | 从列表中获取指定位置的元素 | -| IndexesOfList | 从列表中筛选出指定位置的元素列表 | -| StringArea | 字符串文本框(多行输入区域) | -| ForEachOpen | 循环开始节点 | -| ForEachClose | 循环结束节点 | -| LoadJsonStrToList | json字符串转换为对象列表 | -| GetValueFromJsonObj | 从对象中获取指定key的值 | -| FilterValueForList | 根据指定值过滤列表中元素 | -| SliceList | 列表切片 | -| LoadLocalFilePath | 列出给定路径下的文件列表 | -| LoadImageFromLocalPath | 根据图片全路径加载图片 | -| LoadMaskFromLocalPath | 根据遮罩全路径加载遮罩 | -| IsNoneOrEmpty | 判断是否为空或空字符串或空列表或空字典 | -| IsNoneOrEmptyOptional | 为空时返回指定值(惰性求值),否则返回原值 | -| EmptyOutputNode | 空的输出类型节点 | -| SaveTextToFileByImagePath | 保存文本到图片路径,以图片名作为文件名 | -| CopyAndRenameFiles | 复制或重命名文件 | -| SaveImagesWithoutOutput | 保存图像到指定目录,不是输出类型节点,可用于循环批量跑图和作为惰性求值的前置节点 | -| SaveSingleImageWithoutOutput | 保存单个图像到指定目录,不是输出类型节点,可用于循环批量跑图和作为惰性求值的前置节点 | -| CropTargetSizeImageByBbox | 以bbox的区域中心裁剪指定大小图片 | +| 是否为输出节点 | 名称 | 说明 | +|:----------:|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| × | LoadImageFromURL | 从网络地址加载图片,一行代表一个图片 | +| × | LoadMaskFromURL | 从网络地址加载遮罩,一行代表一个 | +| × | Base64ToImage | 把图片base64字符串转成图片 | +| × | Base64ToMask | 把遮罩图片base64字符串转成遮罩 | +| × | ImageToBase64Advanced | 把图片转成base64字符串, 可以选择图片类型(image, mask) ,方便接口调用判断 | +| × | ImageToBase64 | 把图片转成base64字符串(imageType=["image"]) | +| √ | MaskToBase64Image | 把遮罩转成对应图片的base64字符串(imageType=["mask"]) | +| √ | MaskImageToBase64 | 把遮罩图片转成base64字符串(imageType=["mask"]) | +| × | LoadImageToBase64 | 加载本地图片转成base64字符串 | +| √ | SamAutoMaskSEGS | 得到图片所有语义分割的coco_rle或uncompress_rle格式。
配合ComfyUI-Impact-Pack的SAMLoader或comfyui_segment_anything的SAMModelLoader。
但是如果使用hq模型,必须使用comfyui_segment_anything | +| × | SamAutoMaskSEGSAdvanced | 得到图片所有语义分割的coco_rle或uncompress_rle格式。可以调整sam的参数。 | +| × | MaskToRle | 遮罩图转为coco_rle或uncompress_rle格式,rle格式数据只保存二值,所以无法精准还原遮罩 | +| × | RleToMask | coco_rle或uncompress_rle格式转为遮罩,rle格式数据只保存二值,所以无法精准还原遮罩 | +| × | InsightFaceBBOXDetect | 为图片中的人脸添加序号和区域框 | +| × | ColorPicker | 颜色选择器 | +| × | IntToNumber | 整型转数字 | +| × | StringToList | 字符串转列表 | +| × | IntToList | 整型转列表 | +| × | ListMerge | 列表合并 | +| × | JoinList | 列表根据指定分隔符连接(会先把列表元素转成字符串) | +| √ | ShowString | 显示字符串(可指定消息中key值) | +| √ | ShowInt | 显示整型(可指定消息中key值) | +| √ | ShowFloat | 显示浮点型(可指定消息中key值) | +| √ | ShowNumber | 显示数字(可指定消息中key值) | +| √ | ShowBoolean | 显示布尔值(可指定消息中key值) | +| × | ImageEqual | 图片是否相等(可用于通过判断遮罩图是否全黑来判定是否有遮罩) | +| × | SDBaseVerNumber | 判断SD大模型版本是1.5还是xl | +| × | ListWrapper | 包装成列表(任意类型) | +| × | ListUnWrapper | 转成输出列表,后面连接的节点会把每个元素执行一遍,实现类似遍历效果 | +| × | BboxToCropData | bbox转cropData,方便接入was插件节点使用 | +| × | BboxToBbox | bbox两种格式(x,y,w,h)和(x1,y1,x2,y2)的相互转换 | +| × | BboxesToBboxes | BboxToBbox节点的列表版本 | +| × | SelectBbox | 从Bbox列表中选择一个 | +| × | SelectBboxes | 从Bbox列表中选择多个 | +| × | CropImageByBbox | 根据Bbox区域裁剪图片 | +| × | MaskByBboxes | 根据Bbox列表画遮罩 | +| × | SplitStringToList | 根据分隔符把字符串拆分为某种数据类型(str/int/float/bool)的列表 | +| × | IndexOfList | 从列表中获取指定位置的元素 | +| × | IndexesOfList | 从列表中筛选出指定位置的元素列表 | +| × | StringArea | 字符串文本框(多行输入区域) | +| × | ForEachOpen | 循环开始节点 | +| × | ForEachClose | 循环结束节点 | +| × | LoadJsonStrToList | json字符串转换为对象列表 | +| × | ConvertTypeToAny | 转换数据类型为任意类型,桥接实际具有相同数据类型参数的两节点 | +| × | GetValueFromJsonObj | 从对象中获取指定key的值 | +| × | FilterValueForList | 根据指定值过滤列表中元素 | +| × | SliceList | 列表切片 | +| × | LoadLocalFilePath | 列出给定路径下的文件列表 | +| × | LoadImageFromLocalPath | 根据图片全路径加载图片 | +| × | LoadMaskFromLocalPath | 根据遮罩全路径加载遮罩 | +| × | IsNoneOrEmpty | 判断是否为空或空字符串或空列表或空字典 | +| × | IsNoneOrEmptyOptional | 为空时返回指定值(惰性求值),否则返回原值。由于ComfyUI本体代码逻辑,非字符串会报错 | +| √ | EmptyOutputNode | 空的输出类型节点 | +| × | SaveTextToFileByImagePath | 保存文本到图片路径,以图片名作为文件名 | +| × | CopyAndRenameFiles | 把某个目录下的文件复制到另一个目录并重命名,若目标目录为空值,则重命名原文件 | +| × | SaveImagesWithoutOutput | 保存图像到指定目录,不是输出类型节点,可用于循环批量跑图和作为惰性求值的前置节点 | +| × | SaveSingleImageWithoutOutput | 保存单个图像到指定目录,不是输出类型节点,可用于循环批量跑图和作为惰性求值的前置节点 | +| × | CropTargetSizeImageByBbox | 以bbox区域中心向外裁剪指定宽高图片 | +| × | ConvertToJsonStr | 序列化为json字符串 | ### 示例 ![save api extended](docs/example_note.png) @@ -86,9 +91,14 @@ Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带 ![save api extended](example/example_1.png) ![save api extended](example/example_2.png) ![save api extended](example/example_3.png) + ![save api extended](example/example_4.png) + ![save api extended](example/example_sam_mask.png) ![批量裁剪打标](example/example_image_crop_tag.png) ## 更新记录 +### 2024-11-04 (v1.0.9) +- 新增节点:SamAutoMaskSEGSAdvanced、 MaskToRle、 RleToMask、 ConvertToJsonStr + ### 2024-10-24 (v1.0.7) - 新增节点:SaveTextToFileByImagePath、 CopyAndRenameFiles、 SaveImagesWithoutOutput、 SaveSingleImageWithoutOutput、 CropTargetSizeImageByBbox @@ -99,7 +109,7 @@ Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带 - 新增节点:FilterValueForList ### 2024-09-26 -- 新增节点:GetValueFromJsonObj、 LoadJsonStrToList +- 新增节点:GetValueFromJsonObj、 LoadJsonStrToList、ConvertTypeToAny ### 2024-09-25 [示例](example/example_4.png) - 新增节点:ForEachOpen、 ForEachClose diff --git a/easyapi/ImageNode.py b/easyapi/ImageNode.py index 1feb264..bd0887a 100644 --- a/easyapi/ImageNode.py +++ b/easyapi/ImageNode.py @@ -461,7 +461,7 @@ def INPUT_TYPES(self): "required": { "images": ("IMAGE",), "filename_prefix": ("STRING", {"default": "ComfyUI", - "tooltip": "要保存的文件的前缀。可以使用格式化信息,如%date:yyyy-MM-dd%或%Empty Latent Image.width%"}), + "tooltip": "要保存的文件的前缀。支持的占位符:%width% %height% %year% %month% %day% %hour% %minute% %second%"}), "output_dir": ("STRING", {"default": "", "tooltip": "若为空,存放到output目录"}), }, "optional": { diff --git a/easyapi/SamNode.py b/easyapi/SamNode.py index 5f873eb..d6f3b92 100644 --- a/easyapi/SamNode.py +++ b/easyapi/SamNode.py @@ -1,54 +1,341 @@ +import torch from segment_anything import SamAutomaticMaskGenerator import json import numpy as np +from segment_anything.utils.amg import area_from_rle, mask_to_rle_pytorch, rle_to_mask, batched_mask_to_box, \ + box_xyxy_to_xywh, coco_encode_rle +from pycocotools import mask as mask_utils +import nodes from .util import tensor_to_pil -class SamAutoMaskSEGS: + +class SamAutoMaskSEGSAdvanced: @classmethod def INPUT_TYPES(self): - return {"required": { - "sam_model": ('SAM_MODEL', {}), - "image": ('IMAGE', {}), - "output_mode": (['uncompressed_rle', 'coco_rel'], {"default": "uncompressed_rle"}), - }, + return { + "required": { + "sam_model": ('SAM_MODEL', {}), + "image": ('IMAGE', {}), + }, + "optional": { + "points_per_side": ("INT", + { + "default": 32, + "min": 1, + "max": nodes.MAX_RESOLUTION, + "step": 1, + "tooltip": "沿图像一侧采样的点数。 总点数为points_per_side的平方。优先级盖玉point_grids, 如果为 None,则 'point_grids'采样点必须传" + }), + "points_per_batch": ("INT", + { + "default": 64, + "min": 1, + "max": nodes.MAX_RESOLUTION, + "step": 1, + "tooltip": "设置模型同时执行的点数。 数字越大,速度越快,但会占用更多的 GPU 内存" + }), + "pred_iou_thresh": ("FLOAT", + { + "default": 0.88, + "min": 0, + "max": 1.0, + "step": 0.01, + "tooltip": "置信度阈值。 置信度低于此值的掩码将被忽略" + }), + "stability_score_thresh": ("FLOAT", + { + "default": 0.95, + "min": 0, + "max": 1.0, + "step": 0.01, + "tooltip": "稳定性得分的过滤阈值,范围[0,1]" + }), + "stability_score_offset": ("FLOAT", + { + "default": 1.0, + "min": 0, + "max": 1.0, + "step": 0.01, + "tooltip": "计算稳定性得分时thresh偏移量。\n公式简单理解成 score= (mask > stability_score_thresh+stability_score_offset) / (mask > stability_score_thresh-stability_score_offset)" + }), + "box_nms_thresh": ("FLOAT", + { + "default": 0.7, + "min": 0, + "max": 1.0, + "step": 0.01, + "tooltip": "mask的bbox区域置信度阈值" + }), + "crop_n_layers": ("INT", + { + "default": 0, + "min": 0, + "max": 64, + "step": 1, + "tooltip": "递归重复检测层数,增大此值可以解决多个物体没拆分开的问题,但是速度会变慢" + }), + "crop_nms_thresh": ("FLOAT", + { + "default": 0.7, + "min": 0, + "max": 1.0, + "step": 0.01, + "tooltip": "crop_box区域置信度阈值" + }), + "crop_overlap_ratio": ("FLOAT", + { + "default": 512 / 1500, + "min": 0, + "max": 1.0, + "step": 0.01, + "tooltip": "多层检测时,设置裁剪重叠的程度,第一层使用此值。随着层数增加,重叠程度会减小" + }), + "crop_n_points_downscale_factor": ("INT", + { + "default": 1, + "min": 1, + "max": nodes.MAX_RESOLUTION, + "step": 1, + "tooltip": "用于计算第n层的points_per_side:int(points_per_side/crop_n_points_downscale_factor**n)" + }), + "min_mask_region_area": ("INT", + { + "default": 0, + "min": 0, + "max": nodes.MAX_RESOLUTION, + "step": 1, + "tooltip": "最小区域面积。 用于过滤(忽略)小区域" + }), + "output_mode": (['uncompressed_rle', 'coco_rle'], {"default": "uncompressed_rle"}), + }, } - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("RLE_SEGS", ) + RETURN_TYPES = ("MASK_RLE",) + RETURN_NAMES = ("masks_rle",) FUNCTION = "generate" - OUTPUT_NODE = True + OUTPUT_NODE = False CATEGORY = "EasyApi/Detect" - # INPUT_IS_LIST = False - # OUTPUT_IS_LIST = (False, False) - - def generate(self, sam_model, image, output_mode): + def generate(self, + sam_model, + image, + points_per_side: int = 32, + points_per_batch: int = 64, + pred_iou_thresh: float = 0.88, + stability_score_thresh: float = 0.95, + stability_score_offset: float = 1.0, + box_nms_thresh: float = 0.7, + crop_n_layers: int = 0, + crop_nms_thresh: float = 0.7, + crop_overlap_ratio: float = 512 / 1500, + crop_n_points_downscale_factor: int = 1, + min_mask_region_area: int = 0, + output_mode: str = "uncompressed_rle", + ): + """ + # 沿图像一侧采样的点数。 总点数为 points_per_side**2。优先级盖玉point_grids, 如果为 None,则 'point_grids'采样点必须传。 + points_per_side = 32 + # 设置模型同时执行的点数。 数字越大,速度越快,但会占用更多的 GPU 内存。 + points_per_batch = 64 + # 置信度阈值。 置信度低于此值的掩码将被忽略。 + pred_iou_thresh = 0.88 + # 稳定性得分的过滤阈值,范围[0,1] + stability_score_thresh = 0.95 + # 计算稳定性得分时thresh偏移量 + # 公式简单理解成 score= (mask > stability_score_thresh+stability_score_offset) / (mask > stability_score_thresh-stability_score_offset) + stability_score_offset = 1.0 + # mask的bbox区域置信度阈值。 + box_nms_thresh = 0.7 + # 递归检测次数,增大此值可以解决多个物体没拆分开的问题,但是速度会变慢。 + crop_n_layers = 0 + # crop_box区域置信度阈值。 + crop_nms_thresh = 0.7 + # 设置裁剪重叠的程度,第一层使用此值。随着层数增加,重叠程度会减小。 + crop_overlap_ratio = 512 / 1500 + # 用于计算第n层的points_per_side:按int(points_per_side/crop_n_points_downscale_factor**n)。 + crop_n_points_downscale_factor = 1 + # 用于采样的点列表,归一化为[0,1]。列表中的第n个点用于第n个裁剪层。points_per_side不为空时不生效。Optional[List[np.ndarray]] + point_grids = None + # 最小区域面积。 用于过滤小区域 + min_mask_region_area = 0 + """ + point_grids = None # 判断是不是HQ encodeClassName = sam_model.image_encoder.__class__.__name__ if encodeClassName == "ImageEncoderViTHQ": from custom_nodes.comfyui_segment_anything.sam_hq.automatic import SamAutomaticMaskGeneratorHQ from custom_nodes.comfyui_segment_anything.sam_hq.predictor import SamPredictorHQ samHQ = SamPredictorHQ(sam_model, True) - mask_generator = SamAutomaticMaskGeneratorHQ(samHQ, output_mode=output_mode) + mask_generator = SamAutomaticMaskGeneratorHQ(samHQ, + points_per_side, + points_per_batch, + pred_iou_thresh, + stability_score_thresh, + stability_score_offset, + box_nms_thresh, + crop_n_layers, + crop_nms_thresh, + crop_overlap_ratio, + crop_n_points_downscale_factor, + point_grids, + min_mask_region_area, + output_mode=output_mode) else: - mask_generator = SamAutomaticMaskGenerator(sam_model, output_mode=output_mode) + mask_generator = SamAutomaticMaskGenerator(sam_model, + points_per_side, + points_per_batch, + pred_iou_thresh, + stability_score_thresh, + stability_score_offset, + box_nms_thresh, + crop_n_layers, + crop_nms_thresh, + crop_overlap_ratio, + crop_n_points_downscale_factor, + point_grids, + min_mask_region_area, + output_mode=output_mode) image_pil = tensor_to_pil(image) image_np = np.array(image_pil) image_np_rgb = image_np[..., :3] masks = mask_generator.generate(image_np_rgb) + return (masks,) + + +class SamAutoMaskSEGS(SamAutoMaskSEGSAdvanced): + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "sam_model": ('SAM_MODEL', {}), + "image": ('IMAGE', {}), + "output_mode": (['uncompressed_rle', 'coco_rle'], {"default": "uncompressed_rle"}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("RLE_SEGS",) + + FUNCTION = "generate" + + OUTPUT_NODE = True + CATEGORY = "EasyApi/Detect" + + # INPUT_IS_LIST = False + # OUTPUT_IS_LIST = (False, False) + + def generate(self, sam_model, image, output_mode): + masks = super().generate(sam_model, image, output_mode) masksRle = json.JSONEncoder().encode(masks) return {"ui": {"segsRle": (masksRle,)}, "result": (masksRle,)} +class MaskToRle: + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "mask": ('MASK', {}), + "output_mode": (['uncompressed_rle', 'coco_rle'], {"default": "uncompressed_rle"}), + }, + } + + RETURN_TYPES = ("MASK_RLE",) + RETURN_NAMES = ("masks_rle",) + + FUNCTION = "convert" + + OUTPUT_NODE = False + CATEGORY = "EasyApi/Detect" + + def convert(self, mask, output_mode): + masksRle = [] + b, h, w = mask.shape + rles = mask_to_rle_pytorch((mask > 0.15).bool()) + for i in range(b): + single_rle = rles[i] + area = area_from_rle(single_rle) + bbox = box_xyxy_to_xywh(batched_mask_to_box(mask.bool())[i]).tolist() + # stability_scores = calculate_stability_score(mask[i], mask_threshold, threshold_offset) + if output_mode == "coco_rle": + single_rle = coco_encode_rle(single_rle) + + masksRle.append( + { + "segmentation": single_rle, + # 遮罩区域面积(像素点数) + "area": area, + # 蒙版矩形区域XYWH + "bbox": bbox, + # 用于生成此蒙版的图像的裁剪(XYWH格式) + "crop_box": [0, 0, w, h], + # "predicted_iou": 0.9494854211807251, + # 采样点坐标,自动情况下,蒙版区域内的任意一个点就行 + # "point_coords": [[54.8475,1075.9375]], + # "stability_score": stability_scores.item(), + } + ) + return (masksRle,) + + +class RleToMask: + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "masks_rle": ('MASK_RLE', {}), + "rle_mode": (['uncompressed_rle', 'coco_rle'], {"default": "uncompressed_rle"}), + }, + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("masks",) + + FUNCTION = "convert" + + OUTPUT_NODE = False + CATEGORY = "EasyApi/Detect" + + def convert(self, masks_rle, rle_mode='uncompressed_rle'): + masks = [] + if isinstance(masks_rle, dict): + list_rle = [masks_rle] + else: + list_rle = masks_rle + for mask_rle in list_rle: + if rle_mode == "coco_rle": + mask_np = mask_utils.decode(mask_rle["segmentation"]) + else: + mask_np = rle_to_mask(mask_rle["segmentation"]) + + mask = torch.from_numpy(mask_np).to(torch.float32) + + masks.append(mask.unsqueeze(0)) + + if len(masks) > 1: + # 如果有多个图像,则将它们按维度0拼接在一起 + output_mask = torch.cat(masks, dim=0) + else: + output_mask = masks[0] + + return (output_mask,) + + NODE_CLASS_MAPPINGS = { "SamAutoMaskSEGS": SamAutoMaskSEGS, + "SamAutoMaskSEGSAdvanced": SamAutoMaskSEGSAdvanced, + "MaskToRle": MaskToRle, + "RleToMask": RleToMask, } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { "SamAutoMaskSEGS": "SamAutoMaskSEGS", + "SamAutoMaskSEGSAdvanced": "SamAutoMaskSEGSAdvanced", + "MaskToRle": "MaskToRle", + "RleToMask": "RleToMask", } diff --git a/easyapi/UtilNode.py b/easyapi/UtilNode.py index 72550cc..28771db 100644 --- a/easyapi/UtilNode.py +++ b/easyapi/UtilNode.py @@ -609,6 +609,26 @@ def load_json(self, json_str: str): return ([json],) +class ConvertToJsonStr: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "obj": (any_type, {"forceInput": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("json_str",) + CATEGORY = "EasyApi/Utils" + FUNCTION = "to_json_str" + DESCRIPTION = "将任意对象序列化为json字符串" + + def to_json_str(self, obj): + json = simplejson.dumps(obj, ignore_nan=True) + return (json,) + + class GetValueFromJsonObj: @classmethod def INPUT_TYPES(s): @@ -798,7 +818,7 @@ def INPUT_TYPES(s): RETURN_NAMES = ("any",) FUNCTION = "execute" CATEGORY = "EasyApi/Utils" - DESCRIPTION = "判断输入any是否为None、空列表、空字符串(trim后判断)、空字典,若为true,返回default的值,否则返回输入值" + DESCRIPTION = "判断输入any是否为None、空列表、空字符串(trim后判断)、空字典,若为true,返回default的值,否则返回输入值。受ComfyUI主体代码限制,经测试非字符串会报错" def execute(self, any=None, default=None): if any is None: @@ -971,6 +991,7 @@ def execute(self, directory, save_directory, prefix, name_to_num): "ConvertTypeToAny": ConvertTypeToAny, "GetValueFromJsonObj": GetValueFromJsonObj, "LoadJsonStrToList": LoadJsonStrToList, + "ConvertToJsonStr": ConvertToJsonStr, "FilterValueForList": FilterValueForList, "LoadLocalFilePath": LoadLocalFilePath, "IsNoneOrEmpty": IsNoneOrEmpty, @@ -1006,6 +1027,7 @@ def execute(self, directory, save_directory, prefix, name_to_num): "ConvertTypeToAny": "ConvertTypeToAny", "GetValueFromJsonObj": "GetValueFromJsonObj", "LoadJsonStrToList": "LoadJsonStrToList", + "ConvertToJsonStr": "ConvertToJsonStr", "FilterValueForList": "FilterValueForList", "LoadLocalFilePath": "LoadLocalFilePath", "IsNoneOrEmpty": "IsNoneOrEmpty", diff --git a/example/example_sam_mask.png b/example/example_sam_mask.png new file mode 100644 index 0000000..510a7e0 Binary files /dev/null and b/example/example_sam_mask.png differ diff --git a/pyproject.toml b/pyproject.toml index 42192c1..9516c88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ [project] name = "comfyui-easyapi-nodes" -description = "Provides some features and nodes related to API calls." -version = "1.0.8" +description = "Provides some features and nodes related to API calls. 开发独立应用调用ComfyUI服务的一些补充节点。" +version = "1.0.9" license = { file = "LICENSE" } -dependencies = ["segment_anything", "simple_lama_inpainting", "insightface"] +dependencies = ["segment_anything", "simple_lama_inpainting", "insightface", "simplejson"] [project.urls] Repository = "https://github.com/lldacing/comfyui-easyapi-nodes" diff --git a/requirements.txt b/requirements.txt index 54e5bb8..c22b7bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ segment_anything simple_lama_inpainting -insightface \ No newline at end of file +insightface +simplejson