Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d416ad21f0 | ||
|
|
a46d80b6be | ||
|
|
da57b55c03 | ||
|
|
ebcad2bb54 | ||
|
|
694673bc1c | ||
|
|
2461869aae | ||
|
|
bb9dc79325 | ||
|
|
3939e9d525 | ||
|
|
b36b68a648 | ||
|
|
5d0ad29657 | ||
|
|
25a4420b4f | ||
|
|
bed6ab1df1 | ||
|
|
ff8ba6b209 | ||
|
|
b0e892b083 | ||
|
|
523205b6b4 | ||
|
|
83bbe7b7f7 | ||
|
|
97519d816c | ||
|
|
483b858abe | ||
|
|
f28a3f3ed1 | ||
|
|
e94ece1b1d | ||
|
|
178e9402c9 | ||
|
|
ee25139e53 | ||
|
|
9c1806f71d | ||
|
|
20e360036f | ||
|
|
9d6e210921 | ||
|
|
8bc0caa057 | ||
|
|
07d9b1a225 | ||
|
|
7fb85eb987 | ||
|
|
acfdd7713c | ||
|
|
3c1ea86bc6 | ||
|
|
b8d31fde80 | ||
|
|
631f2f80c9 | ||
|
|
e76a8e634c | ||
|
|
2166920cf0 | ||
|
|
cf32e868d6 | ||
|
|
3c37489c0a | ||
|
|
976dffed60 | ||
|
|
876210a197 | ||
|
|
b869fee891 | ||
|
|
1c82506ab9 | ||
|
|
edb0e409df | ||
|
|
a32f850225 | ||
|
|
918bd85865 | ||
|
|
1be8fa596c | ||
|
|
46dd9f16fd | ||
|
|
3fe0b9ba40 | ||
|
|
5011099081 | ||
|
|
b75a247435 | ||
|
|
cc4997cd94 | ||
|
|
2a4f89dab0 | ||
|
|
e4b331cd93 | ||
|
|
9666ef733b | ||
|
|
b008fa162f | ||
|
|
b498cbd5f8 | ||
|
|
b44511b78d | ||
|
|
9d10c9f5a6 | ||
|
|
df2b4edc65 | ||
|
|
628499ad1c | ||
|
|
ede22dfa27 | ||
|
|
5aaaaffa2e | ||
|
|
aa30d9c495 | ||
|
|
5b7980facd | ||
|
|
c51d1fdea2 | ||
|
|
727f8b87e1 | ||
|
|
60784bf262 | ||
|
|
4cc0273a4c | ||
|
|
d5ec95ec0f | ||
|
|
523be189e3 | ||
|
|
ba7bd1f542 | ||
|
|
7d2f16595a | ||
|
|
82fd658894 | ||
|
|
a52f10255a | ||
|
|
da5b3de3eb | ||
|
|
122d00f8fc | ||
|
|
41715bb263 | ||
|
|
079b65332e | ||
|
|
88cf2a6688 | ||
|
|
cdbcb7f033 | ||
|
|
5c278e8d56 | ||
|
|
4e8daffcd2 | ||
|
|
944051f210 | ||
|
|
0922da0b66 | ||
|
|
e754b97b99 | ||
|
|
80ede24bb5 | ||
|
|
2f63c5c385 | ||
|
|
0adf673854 | ||
|
|
576c52746c | ||
|
|
7410d7c865 | ||
|
|
435558b778 | ||
|
|
701cb45770 | ||
|
|
6f67a49251 | ||
|
|
a9d985c666 | ||
|
|
f543f13668 | ||
|
|
42f6e81bef | ||
|
|
51ee274e40 | ||
|
|
609ccce401 | ||
|
|
c8331f8656 | ||
|
|
dea68c212b | ||
|
|
93580e635a | ||
|
|
b71e6c0900 | ||
|
|
c7bab3cc98 | ||
|
|
503f4a756b | ||
|
|
f641bc15de | ||
|
|
39abd72526 | ||
|
|
d2bf013dcb | ||
|
|
ad516a08b7 | ||
|
|
be4c62b923 | ||
|
|
8119bfd962 | ||
|
|
11054532d2 | ||
|
|
5e0caf6f6f | ||
|
|
7a2f9f95fc | ||
|
|
d5a72214a8 | ||
|
|
7f0766231a | ||
|
|
b5e41f2108 | ||
|
|
fe08a2270b | ||
|
|
775db58e91 | ||
|
|
f5d7f7f575 | ||
|
|
67a650c570 | ||
|
|
847a9c6c7d | ||
|
|
e0f45f51a6 | ||
|
|
5b1eb92c75 | ||
|
|
1657342edd | ||
|
|
c17a0ee889 | ||
|
|
d8f3aaf713 | ||
|
|
5cb59dd1d5 | ||
|
|
de2c27d7d1 | ||
|
|
df5fb224fb | ||
|
|
e9b18dbe48 | ||
|
|
9bbef76417 | ||
|
|
06ed579310 | ||
|
|
02695fe0df | ||
|
|
9b5b2399e1 | ||
|
|
4167733d39 | ||
|
|
6964e11f3d | ||
|
|
e9439f0dc0 | ||
|
|
f1ee79a9ae | ||
|
|
d0118ca742 | ||
|
|
d31c9076c3 | ||
|
|
ac25ebad3c | ||
|
|
9b05d46ff2 | ||
|
|
e514bc1d8a | ||
|
|
ffb0bf5de9 | ||
|
|
1897c3acfd | ||
|
|
7b1dc8ce62 | ||
|
|
d7ee354fe4 | ||
|
|
d4a443607f | ||
|
|
df918829dd | ||
|
|
ba701d1d59 | ||
|
|
12002acd93 | ||
|
|
5d1721d0c3 | ||
|
|
0d4e1ede0f | ||
|
|
a808e30a23 | ||
|
|
913722813e | ||
|
|
49374e012e | ||
|
|
2b69b4f33a | ||
|
|
8ba8c214a3 | ||
|
|
a060322a88 | ||
|
|
52508f0f35 | ||
|
|
a6c4158af3 | ||
|
|
d4a8f0d415 | ||
|
|
a8ca28ebca | ||
|
|
4608de5cbf | ||
|
|
8c14ffdafe | ||
|
|
5ab388d690 | ||
|
|
a78ba7dd35 | ||
|
|
81d97ca81f | ||
|
|
51dcc04be4 | ||
|
|
9bf1e808b2 | ||
|
|
a0e195d1c1 | ||
|
|
6bce70780f | ||
|
|
c1a85e3aa2 | ||
|
|
763cff49e4 | ||
|
|
daaa44eddf | ||
|
|
7f25ce6cd2 | ||
|
|
5d68d617f9 | ||
|
|
400ffebb49 | ||
|
|
99e05344bf | ||
|
|
6a1b5b8d69 | ||
|
|
24ee3ffea5 | ||
|
|
500eae510d | ||
|
|
2e79ec2504 | ||
|
|
33c7e9513f | ||
|
|
ef64db05e3 | ||
|
|
f1ab83f429 | ||
|
|
9e85e3a25c | ||
|
|
6cab7a18e3 | ||
|
|
6d6004ce0d | ||
|
|
625b295a9c | ||
|
|
4145471d24 | ||
|
|
a5e12ff375 | ||
|
|
5f60119ab9 |
+1
-1
@@ -1,3 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: ["https://afdian.net/a/yolain"]
|
||||
custom: ["https://space.bilibili.com/1840885116"]
|
||||
@@ -8,6 +8,8 @@ styles/**
|
||||
workflow/**
|
||||
autocomplete/**
|
||||
web_beta/**
|
||||
web_version/dev/**
|
||||
ComfyUI-Easy-Use-Frontend/
|
||||
docs/**
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
+482
@@ -0,0 +1,482 @@
|
||||

|
||||
|
||||
<div align="center">
|
||||
<a href="https://space.bilibili.com/1840885116">视频介绍</a> |
|
||||
文档 (康明孙) |
|
||||
<a href="https://github.com/yolain/ComfyUI-Yolain-Workflows">工作流合集</a> |
|
||||
<a href="#%EF%B8%8F-donation">捐助</a>
|
||||
<br><br>
|
||||
<a href="./README.md"><img src="https://img.shields.io/badge/🇬🇧English-e9e9e9"></a>
|
||||
<a href="./README.ZH_CN.md"><img src="https://img.shields.io/badge/🇨🇳中文简体-0b8cf5"></a>
|
||||
</div>
|
||||
|
||||
**ComfyUI-Easy-Use** 是一个化繁为简的节点整合包, 在 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的基础上进行延展,并针对了诸多主流的节点包做了整合与优化,以达到更快更方便使用ComfyUI的目的,在保证自由度的同时还原了本属于Stable Diffusion的极致畅快出图体验。
|
||||
|
||||
## 👨🏻🎨 特色介绍
|
||||
|
||||
- 沿用了 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的思路,大大减少了折腾工作流的时间成本。
|
||||
- UI界面美化,首次安装的用户,如需使用UI主题,请在 Settings -> Color Palette 中自行切换主题并**刷新页面**即可
|
||||
- 增加了预采样参数配置的节点,可与采样节点分离,更方便预览。
|
||||
- 支持通配符与Lora的提示词节点,如需使用Lora Block Weight用法,需先保证自定义节点包中安装了 [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack)
|
||||
- 可多选的风格化提示词选择器,默认是Fooocus的样式json,可自定义json放在styles底下,samples文件夹里可放预览图(名称和name一致,图片文件名如有空格需转为下划线'_')
|
||||
- 加载器可开启A1111提示词风格模式,可重现与webui生成近乎相同的图像,需先安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes)
|
||||
- 可使用`easy latentNoisy`或`easy preSamplingNoiseIn`节点实现对潜空间的噪声注入
|
||||
- 简化 SD1.x、SD2.x、SDXL、SVD、Zero123等流程
|
||||
- 简化 Stable Cascade [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#1-13-stable-cascade)
|
||||
- 简化 Layer Diffuse [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-3-layerdiffusion)
|
||||
- 简化 InstantID [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid), 需先保证自定义节点包中安装了 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID)
|
||||
- 简化 IPAdapter, 需先保证自定义节点包中安装最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus)
|
||||
- 扩展 XYplot 的可用性
|
||||
- 整合了Fooocus Inpaint功能
|
||||
- 整合了常用的逻辑计算、转换类型、展示所有类型等
|
||||
- 支持节点上checkpoint、lora模型子目录分类及预览图 (请在设置中开启上下文菜单嵌套子目录)
|
||||
- 支持BriaAI的RMBG-1.4模型的背景去除节点,[技术参考](https://huggingface.co/briaai/RMBG-1.4)
|
||||
- 支持 强制清理comfyUI模型显存占用
|
||||
- 支持Stable Diffusion 3 多账号API节点
|
||||
- 支持IC-Light的应用 [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-5-ic-light) | [代码整合来源](https://github.com/huchenlei/ComfyUI-IC-Light) | [技术参考](https://github.com/lllyasviel/IC-Light)
|
||||
- 中文提示词自动识别,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en)
|
||||
- 支持 sd3 模型
|
||||
- 支持 kolors 模型
|
||||
- 支持 flux 模型
|
||||
- 支持 惰性条件判断(ifElse)和 for循环
|
||||
|
||||
## 👨🏻🔧 安装
|
||||
|
||||
1. 将存储库克隆到 **custom_nodes** 目录并安装依赖
|
||||
```shell
|
||||
#1. git下载
|
||||
git clone https://github.com/yolain/ComfyUI-Easy-Use
|
||||
#2. 安装依赖
|
||||
双击install.bat安装依赖
|
||||
```
|
||||
|
||||
## 📜 更新日志
|
||||
|
||||
**v1.2.5**
|
||||
|
||||
- 在 `easy preSamplingCustom` 和 `easy preSamplingAdvanced` 上增加 `enable (GPU=A1111)` 噪波生成模式选择项
|
||||
- 增加 `easy makeImageForICLora`
|
||||
- 在 `easy ipadapterApply` 添加 `REGULAR - FLUX and SD3.5 only (high strength)` 预置项以支持 InstantX Flux ipadapter
|
||||
- 修复brushnet 无法在 `--fast` 模式下使用
|
||||
- 支持briaai RMBG-2.0
|
||||
- 支持mochi模型
|
||||
- 实现在循环主体中重复使用终端节点输出(例如预览图像和显示任何内容等输出节点...)
|
||||
|
||||
**v1.2.4**
|
||||
|
||||
- 增加 `easy imageSplitTiles` and `easy imageTilesFromBatch` - 图像分块
|
||||
- 支持 `model_override`,`vae_override`,`clip_override` 可以在 `easy fullLoader` 中单独输入
|
||||
- 增加 `easy saveImageLazy`
|
||||
- 增加 `easy loadImageForLoop`
|
||||
- 增加 `easy isFileExist`
|
||||
- 增加 `easy saveText`
|
||||
|
||||
**v1.2.3**
|
||||
|
||||
- `easy showAnything` 和 `easy cleanGPUUsed` 增加输出插槽
|
||||
- 添加新的人体分割在 `easy humanSegmentation` 节点上 - 代码从 [ComfyUI_Human_Parts](https://github.com/metal3d/ComfyUI_Human_Parts) 整合
|
||||
- 当你在 `easy preSamplingCustom` 节点上选择basicGuider,CFG>0 且当前模型为Flux时,将使用FluxGuidance
|
||||
- 增加 `easy loraStackApply` and `easy controlnetStackApply`
|
||||
|
||||
**v1.2.2**
|
||||
|
||||
- 增加 `easy batchAny`
|
||||
- 增加 `easy anythingIndexSwitch`
|
||||
- 增加 `easy forLoopStart` 和 `easy forLoopEnd`
|
||||
- 增加 `easy ifElse`
|
||||
- 增加 v2 版本新前端代码
|
||||
- 增加 `easy fluxLoader`
|
||||
- 增加 `controlnetApply` 相关节点对sd3和hunyuanDiT的支持
|
||||
- 修复 当使用fooocus inpaint后,再使用Lora模型无法生效的问题
|
||||
|
||||
**v1.2.1**
|
||||
|
||||
- 增加 `easy ipadapterApplyFaceIDKolors`
|
||||
- `easy ipadapterApply` 和 `easy ipadapterApplyADV` 增加 **PLUS (kolors genernal)** 和 **FACEID PLUS KOLORS** 预置项
|
||||
- `easy imageRemBg` 增加 **inspyrenet** 选项
|
||||
- 增加 `easy controlnetLoader++`
|
||||
- 去除 `easy positive` `easy negative` 等prompt节点的自动将中文翻译功能,自动翻译仅在 `easy a1111Loader` 等不支持中文TE的加载器中生效
|
||||
- 增加 `easy kolorsLoader` - 可灵加载器,参考了 [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) 和 [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper) 的代码。
|
||||
|
||||
**v1.2.0**
|
||||
|
||||
- 增加 `easy pulIDApply` 和 `easy pulIDApplyADV`
|
||||
- 增加 `easy hunyuanDiTLoader` 和 `easy pixArtLoader`
|
||||
- 当新菜单的位置在上或者下时增加上 crystools 的显示,推荐开两个就好(如果后续crystools有更新UI适配我可能会删除掉)
|
||||
- 增加 **easy sliderControl** - 滑块控制节点,当前可用于控制ipadapterMS的参数 (双击滑块可重置为默认值)
|
||||
- 增加 **layer_weights** 属性在 `easy ipadapterApplyADV` 节点
|
||||
|
||||
**v1.1.9**
|
||||
|
||||
- 增加 新的调度器 **gitsScheduler**
|
||||
- 增加 `easy imageBatchToImageList` 和 `easy imageListToImageBatch` (修复Impact版的一点小问题)
|
||||
- 递归模型子目录嵌套
|
||||
- 支持 sd3 模型
|
||||
- 增加 `easy applyInpaint` - 局部重绘全模式节点 (相比与之前的kSamplerInpating节点逻辑会更合理些)
|
||||
|
||||
**v1.1.8**
|
||||
|
||||
- 增加中文提示词自动翻译,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en), 默认已对wildcard、lora正则处理, 其他需要保留的中文,可使用`@你的提示词@`包裹 (若依赖安装完成后报错, 请重启),测算大约会占0.3GB显存
|
||||
- 增加 `easy controlnetStack` - controlnet堆
|
||||
- 增加 `easy applyBrushNet` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json)
|
||||
- 增加 `easy applyPowerPaint` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json)
|
||||
|
||||
**v1.1.7**
|
||||
|
||||
- 修复 一些模型(如controlnet模型等)未成功写入缓存,导致修改前置节点束参数(如提示词)需要二次载入模型的问题
|
||||
- 增加 `easy prompt` - 主体和光影预置项,后期可能会调整
|
||||
- 增加 `easy icLightApply` - 重绘光影, 从[ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)优化
|
||||
- 增加 `easy imageSplitGrid` - 图像网格拆分
|
||||
- `easy kSamplerInpainting` 的 **additional** 属性增加差异扩散和brushnet等相关选项
|
||||
- 增加 brushnet模型加载的支持 - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
|
||||
- 增加 `easy applyFooocusInpaint` - Fooocus内补节点 替代原有的 FooocusInpaintLoader
|
||||
- 移除 `easy fooocusInpaintLoader` - 容易bug,不再使用
|
||||
- 修改 easy kSampler等采样器中并联的model 不再替换输出中pipe里的model
|
||||
|
||||
**v1.1.6**
|
||||
|
||||
- 增加步调齐整适配 - 在所有的预采样和全采样器节点中的 调度器(schedulder) 增加了 **alignYourSteps** 选项
|
||||
- `easy kSampler` 和 `easy fullkSampler` 的 **image_output** 增加 **Preview&Choose**选项
|
||||
- 增加 `easy styleAlignedBatchAlign` - 风格对齐 [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy)
|
||||
- 增加 `easy ckptNames`
|
||||
- 增加 `easy controlnetNames`
|
||||
- 增加 `easy imagesSplitimage` - 批次图像拆分单张
|
||||
- 增加 `easy imageCount` - 图像数量
|
||||
- 增加 `easy textSwitch` - 文字切换
|
||||
|
||||
**v1.1.5**
|
||||
|
||||
- 重写 `easy cleanGPUUsed` - 可强制清理comfyUI的模型显存占用
|
||||
- 增加 `easy humanSegmentation` - 多类分割、人像分割
|
||||
- 增加 `easy imageColorMatch`
|
||||
- 增加 `easy ipadapterApplyRegional`
|
||||
- 增加 `easy ipadapterApplyFromParams`
|
||||
- 增加 `easy imageInterrogator` - 图像反推
|
||||
- 增加 `easy stableDiffusion3API` - 简易的Stable Diffusion 3 多账号API节点
|
||||
|
||||
**v1.1.4**
|
||||
|
||||
- 增加 `easy imageChooser` - 从[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker)简化的图片选择器
|
||||
- 增加 `easy preSamplingCustom` - 自定义预采样,可支持cosXL-edit
|
||||
- 增加 `easy ipadapterStyleComposition`
|
||||
- 增加 在Loaders上右键菜单可查看 checkpoints、lora 信息
|
||||
- 修复 `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` 以兼容ComfyUI Revision>=2098 [0542088e] 以上版本
|
||||
- 修复 FooocusInpaint修改ModelPatcher计算权重引发的问题,理应在生成model后重置ModelPatcher为默认值
|
||||
|
||||
**v1.1.3**
|
||||
|
||||
- `easy ipadapterApply` 增加 **COMPOSITION** 预置项
|
||||
- 增加 对[ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) lora模型 的加载支持
|
||||
- 增加 `easy promptLine`
|
||||
- 增加 `easy promptReplace`
|
||||
- 增加 `easy promptConcat`
|
||||
- `easy wildcards` 增加 **multiline_mode**属性
|
||||
- 增加 当节点需要下载模型时,若huggingface连接超时,会切换至镜像地址下载模型
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.2</b></summary>
|
||||
|
||||
- 改写 EasyUse 相关节点的部分插槽推荐节点
|
||||
- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图
|
||||
- 增加 `easy sv3dLoader`
|
||||
- 增加 `easy dynamiCrafterLoader`
|
||||
- 增加 `easy ipadapterApply`
|
||||
- 增加 `easy ipadapterApplyADV`
|
||||
- 增加 `easy ipadapterApplyEncoder`
|
||||
- 增加 `easy ipadapterApplyEmbeds`
|
||||
- 增加 `easy preMaskDetailerFix`
|
||||
- `easy kSamplerInpainting` 增加 **additional** 属性,可设置成 Differential Diffusion 或 Only InpaintModelConditioning
|
||||
- 修复 `easy stylesSelector` 当未选择样式时,原有提示词发生了变化
|
||||
- 修复 `easy pipeEdit` 提示词输入lora时报错
|
||||
- 修复 layerDiffuse xyplot相关bug
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.1/b></summary>
|
||||
|
||||
- 修复首次添加含seed的节点且当前模式为control_before_generate时,seed为0的问题
|
||||
- `easy preSamplingAdvanced` 增加 **return_with_leftover_noise**
|
||||
- 修复 `easy stylesSelector` 当选择自定义样式文件时运行队列报错
|
||||
- `easy preSamplingLayerDiffusion` 增加 mask 可选传入参数
|
||||
- 将所有 **seed_num** 调整回 **seed**
|
||||
- 修补官方BUG: 当control_mode为before 在首次加载页面时未修改节点中widget名称为 control_before_generate
|
||||
- 去除强制**control_before_generate**设定
|
||||
- 增加 `easy imageRemBg` - 默认为BriaAI的RMBG-1.4模型, 移除背景效果更加,速度更快
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.0</b></summary>
|
||||
|
||||
- 增加 `easy imageSplitList` - 拆分每 N 张图像
|
||||
- 增加 `easy preSamplingDiffusionADDTL` - 可配置前景、背景、blended的additional_prompt等
|
||||
- 增加 `easy preSamplingNoiseIn` 可替代需要前置的`easy latentNoisy`节点 实现效果更好的噪声注入
|
||||
- `easy pipeEdit` 增加 条件拼接模式选择,可选择替换、合并、联结、平均、设置条件时间
|
||||
- 增加 `easy pipeEdit` - 可编辑Pipe的节点(包含可重新输入提示词)
|
||||
- 增加 `easy preSamplingLayerDiffusion` 与 `easy kSamplerLayerDiffusion` (连接 `easy kSampler` 也能通)
|
||||
- 增加 在 加载器、预采样、采样器、Controlnet等节点上右键可快速替换同类型节点的便捷菜单
|
||||
- 增加 `easy instantIDApplyADV` 可连入 positive 与 negative
|
||||
- 修复 `easy wildcards` 读取lora未填写完整路径时未自动检索导致加载lora失败的问题
|
||||
- 修复 `easy instantIDApply` mask 未传入正确值
|
||||
- 修复 在 非a1111提示词风格下 BREAK 不生效的问题
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.9</b></summary>
|
||||
|
||||
- 修复未安装 ComfyUI-Impack-Pack 和 ComfyUI_InstantID 时报错
|
||||
- 修复 `easy pipeIn` - pipe设为可不必选
|
||||
- 增加 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid)
|
||||
- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表
|
||||
- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.8</b></summary>
|
||||
|
||||
- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints))
|
||||
- `easy styleSelector` 搜索框修改为不区分大小写匹配
|
||||
- `easy fullLoader` 增加 **positive**、**negative**、**latent** 输出项
|
||||
- 修复 SDXLClipModel 在 ComfyUI 修订版本号 2016[c2cb8e88] 及以上的报错(判断了版本号可兼容老版本)
|
||||
- 修复 `easy detailerFix` 批次大小大于1时生成出错
|
||||
- 修复`easy preSampling`等 latent传入后无法根据批次索引生成的问题
|
||||
- 修复 `easy svdLoader` 报错
|
||||
- 优化代码,减少了诸多冗余,提升运行速度
|
||||
- 去除中文翻译对照文本
|
||||
|
||||
(翻译对照已由 [AIGODLIKE-COMFYUI-TRANSLATION](https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation) 统一维护啦!
|
||||
首次下载或者版本较早的朋友请更新 AIGODLIKE-COMFYUI-TRANSLATION 和本节点包至最新版本。)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.7</b></summary>
|
||||
|
||||
- 增加 `easy cascadeLoader` - stable cascade 加载器
|
||||
- 增加 `easy preSamplingCascade` - stabled cascade stage_c 预采样参数
|
||||
- 增加 `easy fullCascadeKSampler` - stable cascade stage_c 完整版采样器
|
||||
- 增加 `easy cascadeKSampler` - stable cascade stage-c ksampler simple
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.6</b></summary>
|
||||
|
||||
- 增加 `easy XYInputs: Checkpoint`
|
||||
- 增加 `easy XYInputs: Lora`
|
||||
- `easy seed` 增加固定种子值时可手动切换随机种
|
||||
- 修复 `easy fullLoader`等加载器切换lora时自动调整节点大小的问题
|
||||
- 去除原有ttn的图片保存逻辑并适配ComfyUI默认的图片保存格式化扩展
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.5</b></summary>
|
||||
|
||||
- 增加 `easy isSDXL`
|
||||
- `easy svdLoader` 增加提示词控制, 可配合open_clip模型进行使用
|
||||
- `easy wildcards` 增加 **populated_text** 可输出通配填充后文本
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.4</b></summary>
|
||||
|
||||
- 增加 `easy showLoaderSettingsNames` 可显示与输出加载器部件中的 模型与VAE名称
|
||||
- 增加 `easy promptList` - 提示词列表
|
||||
- 增加 `easy fooocusInpaintLoader` - Fooocus内补节点(仅支持XL模型的流程)
|
||||
- 增加 **Logic** 逻辑类节点 - 包含类型、计算、判断和转换类型等
|
||||
- 增加 `easy imageSave` - 带日期转换和宽高格式化的图像保存节点
|
||||
- 增加 `easy joinImageBatch` - 合并图像批次
|
||||
- `easy showAnything` 增加支持转换其他类型(如:tensor类型的条件、图像等)
|
||||
- `easy kSamplerInpainting` 增加 **patch** 传入值,配合Fooocus内补节点使用
|
||||
- `easy imageSave` 增加 **only_preivew**
|
||||
|
||||
- 修复 xyplot在pillow>9.5中报错
|
||||
- 修复 `easy wildcards` 在使用PS扩展插件运行时报错
|
||||
- 修复 `easy latentCompositeMaskedWithCond`
|
||||
- 修复 `easy XYInputs: ControlNet` 报错
|
||||
- 修复 `easy loraStack` **toggle** 为 disabled 时报错
|
||||
|
||||
- 修改首次安装节点包不再自动替换主题,需手动调整并刷新页面
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.3</b></summary>
|
||||
|
||||
- 增加 `easy stylesSelector` 风格化提示词选择器
|
||||
- 增加队列进度条设置项,默认为未启用状态
|
||||
- `easy controlnetLoader` 和 `easy controlnetLoaderADV` 增加参数 **scale_soft_weights**
|
||||
|
||||
|
||||
- 修复 `easy XYInputs: Sampler/Scheduler` 报错
|
||||
- 修复 右侧菜单 点击按钮时老是跑位的问题
|
||||
- 修复 styles 路径在其他环境报错
|
||||
- 修复 `easy comfyLoader` 读取错误
|
||||
- 修复 xyPlot 在连接 zero123 时报错
|
||||
- 修复加载器中提示词为组件时报错
|
||||
- 修复 `easy getNode` 和 `easy setNode` 加载时标题未更改
|
||||
- 修复所有采样器中存储图片使用子目录前缀不生效的问题
|
||||
|
||||
|
||||
- 调整UI主题
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.2</b></summary>
|
||||
|
||||
- 增加 **autocomplete** 文件夹,如果您安装了 [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts), 将在启动时合并该文件夹下的所有txt文件并覆盖到pyssss包里的autocomplete.txt文件。
|
||||
- 增加 `easy XYPlotAdvanced` 和 `easy XYInputs` 等相关节点
|
||||
- 增加 **Alt+1到9** 快捷键,可快速粘贴 Node templates 的节点预设 (对应 1到9 顺序)
|
||||
|
||||
- 修复 `easy imageInsetCrop` 测量值为百分比时步进为1
|
||||
- 修复 开启 `a1111_prompt_style` 时XY图表无法使用的问题
|
||||
- 右键菜单中增加了一个 `📜Groups Map(EasyUse)`
|
||||
|
||||
- 修复在Comfy新版本中UI加载失败
|
||||
- 修复 `easy pipeToBasicPipe` 报错
|
||||
- 修改 `easy fullLoader` 和 `easy a1111Loader` 中的 **a1111_prompt_style** 默认值为 False
|
||||
- `easy XYInputs ModelMergeBlocks` 支持csv文件导入数值
|
||||
|
||||
- 替换了XY图生成时的字体文件
|
||||
|
||||
- 移除 `easy imageRemBg`
|
||||
- 移除包中的介绍图和工作流文件,减少包体积
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.1</b></summary>
|
||||
|
||||
- 新增 `easy seed` - 简易随机种
|
||||
- `easy preDetailerFix` 新增了 `optional_image` 传入图像可选,如未传默认取值为pipe里的图像
|
||||
- 新增 `easy kSamplerInpainting` 用于内补潜空间的采样器
|
||||
- 新增 `easy pipeToBasicPipe` 用于转换到Impact的某些节点上
|
||||
|
||||
- 修复 `easy comfyLoader` 报错
|
||||
- 修复所有包含输出图片尺寸的节点取值方式无法批处理的问题
|
||||
- 修复 `width` 和 `height` 无法在 `easy svdLoader` 自定义的报错问题
|
||||
- 修复所有采样器预览图片的地址链接 (解决在 MACOS 系统中图片无法在采样器中预览的问题)
|
||||
- 修复 `vae_name` 在 `easy fullLoader` 和 `easy a1111Loader` 和 `easy comfyLoader` 中选择但未替换原始vae问题
|
||||
- 修复 `easy fullkSampler` 除pipe外其他输出值的报错
|
||||
- 修复 `easy hiresFix` 输入连接pipe和image、vae同时存在时报错
|
||||
- 修复 `easy fullLoader` 中 `model_override` 连接后未执行
|
||||
- 修复 因新增`easy seed` 导致action错误
|
||||
- 修复 `easy xyplot` 的字体文件路径读取错误
|
||||
- 修复 convert 到 `easy seed` 随机种无法固定的问题
|
||||
- 修复 `easy pipeIn` 值传入的报错问题
|
||||
- 修复 `easy zero123Loader` 和 `easy svdLoader` 读取模型时将模型加入到缓存中
|
||||
- 修复 `easy kSampler` `easy kSamplerTiled` `easy detailerFix` 的 `image_output` 默认值为 Preview
|
||||
- `easy fullLoader` 和 `easy a1111Loader` 新增了 `a1111_prompt_style` 参数可以重现和webui生成相同的图像,当前您需要安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) 才能使用此功能
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.0</b></summary>
|
||||
|
||||
- 新增`easy positive` - 简易正面提示词文本
|
||||
- 新增`easy negative` - 简易负面提示词文本
|
||||
- 新增`easy wildcards` - 支持通配符和Lora选择的提示词文本
|
||||
- 新增`easy portraitMaster` - 肖像大师v2.2
|
||||
- 新增`easy loraStack` - Lora堆
|
||||
- 新增`easy fullLoader` - 完整版的加载器
|
||||
- 新增`easy zero123Loader` - 简易zero123加载器
|
||||
- 新增`easy svdLoader` - 简易svd加载器
|
||||
- 新增`easy fullkSampler` - 完整版的采样器(无分离)
|
||||
- 新增`easy hiresFix` - 支持Pipe的高清修复
|
||||
- 新增`easy predetailerFix` `easy DetailerFix` - 支持Pipe的细节修复
|
||||
- 新增`easy ultralyticsDetectorPipe` `easy samLoaderPipe` - 检测加载器(细节修复的输入项)
|
||||
- 新增`easy pipein` `easy pipeout` - Pipe的输入与输出
|
||||
- 新增`easy xyPlot` - 简易的xyplot (后续会更新更多可控参数)
|
||||
- 新增`easy imageRemoveBG` - 图像去除背景
|
||||
- 新增`easy imagePixelPerfect` - 图像完美像素
|
||||
- 新增`easy poseEditor` - 姿势编辑器
|
||||
- 新增UI主题(黑曜石)- 默认自动加载UI, 也可在设置中自行更替
|
||||
|
||||
- 修复 `easy globalSeed` 不生效问题
|
||||
- 修复所有的`seed_num` 因 [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) 实时更新图表导致值错乱的问题
|
||||
- 修复`easy imageSize` `easy imageSizeBySide` `easy imageSizeByLongerSide` 可作为终节点
|
||||
- 修复 `seed_num` (随机种子值) 在历史记录中读取无法一致的Bug
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary><b>v0.5</b></summary>
|
||||
|
||||
- 新增 `easy controlnetLoaderADV` 节点
|
||||
- 新增 `easy imageSizeBySide` 节点,可选输出为长边或短边
|
||||
- 新增 `easy LLLiteLoader` 节点,如果您预先安装过 kohya-ss/ControlNet-LLLite-ComfyUI 包,请将 models 里的模型文件移动至 ComfyUI\models\controlnet\ (即comfy默认的controlnet路径里,请勿修改模型的文件名,不然会读取不到)。
|
||||
- 新增 `easy imageSize` 和 `easy imageSizeByLongerSize` 输出的尺寸显示。
|
||||
- 新增 `easy showSpentTime` 节点用于展示图片推理花费时间与VAE解码花费时间。
|
||||
- `easy controlnetLoaderADV` 和 `easy controlnetLoader` 新增 `control_net` 可选传入参数
|
||||
- `easy preSampling` 和 `easy preSamplingAdvanced` 新增 `image_to_latent` 可选传入参数
|
||||
- `easy a1111Loader` 和 `easy comfyLoader` 新增 `batch_size` 传入参数
|
||||
|
||||
- 修改 `easy controlnetLoader` 到 loader 分类底下。
|
||||
</details>
|
||||
|
||||
## 整合参考到的相关节点包
|
||||
|
||||
声明: 非常尊重这些原作者们的付出,开源不易,我仅仅只是做了一些整合与优化。
|
||||
|
||||
| 节点名 (搜索名) | 相关的库 | 库相关的节点 |
|
||||
|:-------------------------------|:----------------------------------------------------------------------------|:------------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy if | [ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) | IfExecute |
|
||||
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
|
||||
| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
|
||||
| easy icLightApply | [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light) | ICLightApply等 |
|
||||
| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader |
|
||||
|
||||
## Credits
|
||||
|
||||
[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - 功能强大且模块化的Stable Diffusion GUI
|
||||
|
||||
[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI管理器
|
||||
|
||||
[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - 管道节点(节点束)让用户减少了不必要的连接
|
||||
|
||||
[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - diffus3的获取与设置点让用户可以分离工作流构成
|
||||
|
||||
[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - 常规整合包1
|
||||
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - 常规整合包2
|
||||
|
||||
[ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) - ComfyUI逻辑运算
|
||||
|
||||
[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - 让模型生成不受训练分辨率限制
|
||||
|
||||
[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - 风格迁移
|
||||
|
||||
[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - 人脸迁移
|
||||
|
||||
[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - 人脸迁移
|
||||
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss 小蛇🐍脚本
|
||||
|
||||
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - 图片选择器
|
||||
|
||||
[ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) - BrushNet 内补节点
|
||||
|
||||
[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT架构相关节点(Pixart、混元DiT等)
|
||||
|
||||
## ☕️ Donation
|
||||
|
||||
**Comfyui-Easy-Use** 是一个 GPL 许可的开源项目。为了项目取得更好、可持续的发展,我希望能够获得更多的支持。 如果我的自定义节点为您的一天增添了价值,请考虑喝杯咖啡来进一步补充能量! 💖感谢您的支持,每一杯咖啡都是我创作的动力!
|
||||
|
||||
- [BiliBili充电](https://space.bilibili.com/1840885116)
|
||||
- [爱发电](https://afdian.com/a/yolain)
|
||||
- [Wechat/Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03)
|
||||
|
||||
感谢您的捐助,我将用这些费用来租用 GPU 或购买其他 GPT 服务,以便更好地调试和完善 ComfyUI-Easy-Use 功能
|
||||
|
||||
## 🌟Stargazers
|
||||
|
||||
My gratitude extends to the generous souls who bestow a star. Your support is much appreciated!
|
||||
|
||||
[](https://github.com/yolain/ComfyUI-Easy-Use/stargazers)
|
||||
-423
@@ -1,423 +0,0 @@
|
||||
<p align="right">
|
||||
<a href="./README.md">中文</a> | <strong>English</strong>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
# ComfyUI Easy Use
|
||||
</div>
|
||||
|
||||
**ComfyUI-Easy-Use** is a simplified node integration package, which is extended on the basis of [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes), and has been integrated and optimized for many mainstream node packages to achieve the purpose of faster and more convenient use of ComfyUI. While ensuring the degree of freedom, it restores the ultimate smooth image production experience that belongs to Stable Diffusion.
|
||||
|
||||
[](https://github.com/yolain/ComfyUI-Yolain-Workflows)
|
||||
|
||||
## 👨🏻🎨 Introduce
|
||||
|
||||
- Inspire by [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes), which greatly reduces the time cost of tossing workflows。
|
||||
- UI interface beautification, the first time you install the user, if you need to use the UI theme, please switch the theme in Settings -> Color Palette and refresh page.
|
||||
- Added a node for pre-sampling parameter configuration, which can be separated from the sampling node for easier previewing
|
||||
- Wildcards and lora's are supported, for Lora Block Weight usage, ensure that the custom node package has the [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack)
|
||||
- Multi-selectable styled cue word selector, default is Fooocus style json, custom json can be placed under styles, samples folder can be placed in the preview image (name and name consistent, image file name such as spaces need to be converted to underscores '_')
|
||||
- The loader enables the A1111 prompt mode, which reproduces nearly identical images to those generated by webui, and needs to be installed [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) first.
|
||||
- Noise injection into the latent space can be achieved using the `easy latentNoisy` or `easy preSamplingNoiseIn` node
|
||||
- Simplified processes for SD1.x, SD2.x, SDXL, SVD, Zero123, etc. [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableDiffusion)
|
||||
- Simplified Stable Cascade [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
|
||||
- Simplified Layer Diffuse [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#LayerDiffusion),The first time you use it you may need to run `pip install -r requirements.txt` to install the required dependencies.
|
||||
- Simplified InstantID [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID), You need to make sure that the custom node package has the [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID)
|
||||
- Extending the usability of XYplot
|
||||
- Fooocus Inpaint integration
|
||||
- Integration of common logical calculations, conversion of types, display of all types, etc.
|
||||
- Background removal nodes for the RMBG-1.4 model supporting BriaAI, [BriaAI Guide](https://huggingface.co/briaai/RMBG-1.4)
|
||||
- Forcibly cleared the memory usage of the comfy UI model are supported
|
||||
- Stable Diffusion 3 multi-account API nodes are supported
|
||||
- Support Stable Diffusion 3 model
|
||||
- Support Kolors model
|
||||
|
||||
## 👨🏻🔧 Installation
|
||||
Clone the repo into the **custom_nodes** directory and install the requirements:
|
||||
```shell
|
||||
#1. Clone the repo
|
||||
git clone https://github.com/yolain/ComfyUI-Easy-Use
|
||||
#2. Install the requirements
|
||||
Double-click install.bat to install the required dependencies
|
||||
```
|
||||
|
||||
## ☕️ Plan
|
||||
|
||||
- [ ] Updated new front-end code for easier maintenance
|
||||
- [x] Maintain css styles using sass
|
||||
- [ ] Optimize existing extensions
|
||||
- [ ] Add new components
|
||||
- [ ] Add light theme
|
||||
- [ ] Upload new workflows to [ComfyUI-Yolain-Workflows](https://github.com/yolain/ComfyUI-Yolain-Workflows) and translate readme to english version.
|
||||
- [ ] Write gitbook with more detailed function introdution
|
||||
|
||||
## 📜 Changelog
|
||||
|
||||
**v1.2.1**
|
||||
|
||||
- Added `easy ipadapterApplyFaceIDKolors`
|
||||
- Added **inspyrenet** to `easy imageRemBg`
|
||||
- Added `easy controlnetLoader++`
|
||||
- Added **PLUS (kolors genernal)** and **FACEID PLUS KOLORS** preset to `easy ipadapterApply` and `easy ipadapterApplyADV` (Supported kolors ipadapter)
|
||||
- Added `easy kolorsLoader` - Code based on [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ)'s and [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper)'s repo, thanks for their contribution.
|
||||
|
||||
**v1.2.0**
|
||||
|
||||
- Added `easy pulIDApply` and `easy pulIDApplyADV`
|
||||
- Added `easy huanyuanDiTLoader` and `easy pixArtLoader`
|
||||
- Added **easy sliderControl** - Slider control node, which can currently be used to control the parameters of ipadapterMS (double-click the slider to reset to default)
|
||||
- Added **layer_weights** in `easy ipadapterApplyADV`
|
||||
|
||||
**v1.1.9**
|
||||
|
||||
- Added **gitsScheduler**
|
||||
- Added `easy imageBatchToImageList` and `easy imageListToImageBatch`
|
||||
- Recursive subcategories nested for models
|
||||
- Support for Stable Diffusion 3 model
|
||||
- Added `easy applyInpaint` - All inpainting mode in this node
|
||||
|
||||
**v1.1.8**
|
||||
|
||||
- Added `easy controlnetStack`
|
||||
- Added `easy applyBrushNet` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json)
|
||||
- Added `easy applyPowerPaint` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json)
|
||||
|
||||
**v1.1.7**
|
||||
|
||||
- Added `easy prompt` - Subject and light presets, maybe adjusted later
|
||||
- Added `easy icLightApply` - Light and shadow migration, Code based on [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)
|
||||
- Added `easy imageSplitGrid`
|
||||
- `easy kSamplerInpainting` added options such as different diffusion and brushnet in **additional** widget
|
||||
- Support for brushnet model loading - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
|
||||
- Added `easy applyFooocusInpaint` - Replace FooocusInpaintLoader
|
||||
- Removed `easy fooocusInpaintLoader`
|
||||
|
||||
**v1.1.6**
|
||||
|
||||
- Added **alignYourSteps** to **schedulder** widget in all `easy preSampling` and `easy fullkSampler`
|
||||
- Added **Preview&Choose** to **image_output** widget in `easy kSampler` & `easy fullkSampler`
|
||||
- Added `easy styleAlignedBatchAlign` - Credit of [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy)
|
||||
- Added `easy ckptNames`
|
||||
- Added `easy controlnetNames`
|
||||
- Added `easy imagesSplitimage` - Batch images split into single images
|
||||
- Added `easy imageCount` - Get Image Count
|
||||
- Added `easy textSwitch` - Text Switch
|
||||
|
||||
**v1.1.5**
|
||||
|
||||
- Rewrite `easy cleanGPUUsed` - the memory usage of the comfyUI can to be cleared
|
||||
- Added `easy humanSegmentation` - Human Part Segmentation
|
||||
- Added `easy imageColorMatch`
|
||||
- Added `easy ipadapterApplyRegional`
|
||||
- Added `easy ipadapterApplyFromParams`
|
||||
- Added `easy imageInterrogator` - Image To Prompt
|
||||
- Added `easy stableDiffusion3API` - Easy Stable Diffusion 3 Multiple accounts API Node
|
||||
|
||||
**v1.1.4**
|
||||
|
||||
- Added `easy preSamplingCustom` - Custom-PreSampling, can be supported cosXL-edit
|
||||
- Added `easy ipadapterStyleComposition`
|
||||
- Added the right-click menu to view checkpoints and lora information in all Loaders
|
||||
- Fixed `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` compatible with ComfyUI Revision>=2098 [0542088e] or later
|
||||
|
||||
|
||||
**v1.1.3**
|
||||
|
||||
- `easy ipadapterApply` Added **COMPOSITION** preset
|
||||
- Supported [ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) when load ResAdapter lora
|
||||
- Added `easy promptLine`
|
||||
- Added `easy promptReplace`
|
||||
- Added `easy promptConcat`
|
||||
- `easy wildcards` Added **multiline_mode**
|
||||
|
||||
**v1.1.2**
|
||||
|
||||
- Optimized some of the recommended nodes for slots related to EasyUse
|
||||
- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews
|
||||
- Added `easy sv3dLoader`
|
||||
- Added `easy dynamiCrafterLoader`
|
||||
- Added `easy ipadapterApply`
|
||||
- Added `easy ipadapterApplyADV`
|
||||
- Added `easy ipadapterApplyEncoder`
|
||||
- Added `easy ipadapterApplyEmbeds`
|
||||
- Added `easy preMaskDetailerFix`
|
||||
- Fixed `easy stylesSelector` is change the prompt when not select the style
|
||||
- Fixed `easy pipeEdit` error when add lora to prompt
|
||||
- Fixed layerDiffuse xyplot bug
|
||||
- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning'
|
||||
|
||||
**v1.1.1**
|
||||
|
||||
- The issue that the seed is 0 when a node with a seed control is added and **control before generate** is fixed for the first time run queue prompt.
|
||||
- `easy preSamplingAdvanced` Added **return_with_leftover_noise**
|
||||
- Fixed `easy stylesSelector` error when choose the custom file
|
||||
- `easy preSamplingLayerDiffusion` Added optional input parameter for mask
|
||||
- Renamed all nodes widget name named seed_num to seed
|
||||
- Remove forced **control_before_generate** settings。 If you want to use control_before_generate, change widget_value_control_mode to before in system settings
|
||||
- Added `easy imageRemBg` - The default is BriaAI's RMBG-1.4 model, which removes the background effect more and faster
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.0</b></summary>
|
||||
|
||||
- Added `easy imageSplitList` - to split every N images
|
||||
- Added `easy preSamplingDiffusionADDTL` - It can modify foreground、background or blended additional prompt
|
||||
- Added `easy preSamplingNoiseIn` It can replace the `easy latentNoisy` node that needs to be fronted to achieve better noise injection
|
||||
- `easy pipeEdit` Added conditioning splicing mode selection, you can choose to replace, concat, combine, average, and set timestep range
|
||||
- Added `easy pipeEdit` - nodes that can edit pipes (including re-enterable prompts)
|
||||
- Added `easy preSamplingLayerDiffusion` and `easy kSamplerLayerDiffusion`
|
||||
- Added a convenient menu to right-click on nodes such as Loader, Presampler, Sampler, Controlnet, etc. to quickly replace nodes of the same type
|
||||
- Added `easy instantIDApplyADV` can link positive and negative
|
||||
- Fixed layerDiffusion error when batch size greater than 1
|
||||
- Fixed `easy wildcards` When LoRa is not filled in completely, LoRa is not automatically retrieved, resulting in failure to load LoRa
|
||||
- Fixed the issue that 'BREAK' non-initiation when didn't use a1111 prompt style
|
||||
- Fixed `easy instantIDApply` mask not input right
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.9</b></summary>
|
||||
|
||||
- Fixed the error when ComfyUI-Impack-Pack and ComfyUI_InstantID were not installed
|
||||
- Fixed `easy pipeIn`
|
||||
- Added `easy instantIDApply` - you need installed [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) fisrt, Workflow[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#InstantID)
|
||||
- Fixed `easy detailerFix` not added to the list of nodes available for saving images formatting extensions
|
||||
- Fixed `easy XYInputs: PromptSR` errors are reported when replacing negative prompts
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.8</b></summary>
|
||||
|
||||
- `easy cascadeLoader` stage_c and stage_b support the checkpoint model (Download [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models)
|
||||
- `easy styleSelector` The search box is modified to be case-insensitive
|
||||
- `easy fullLoader` **positive**、**negative**、**latent** added to the output items
|
||||
- Fixed the issue that 'easy preSampling' and other similar node, latent could not be generated based on the batch index after passing in
|
||||
- Fixed `easy svdLoader` error when the positive or negative is empty
|
||||
- Fixed the error of SDXLClipModel in ComfyUI revision 2016[c2cb8e88] and above (the revision number was judged to be compatible with the old revision)
|
||||
- Fixed `easy detailerFix` generation error when batch size is greater than 1
|
||||
- Optimize the code, reduce a lot of redundant code and improve the running speed
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.7</b></summary>
|
||||
|
||||
- Added `easy cascadeLoader` - stable cascade Loader
|
||||
- Added `easy preSamplingCascade` - stable cascade preSampling Settings
|
||||
- Added `easy fullCascadeKSampler` - stable cascade stage-c ksampler full
|
||||
- Added `easy cascadeKSampler` - stable cascade stage-c ksampler simple
|
||||
-
|
||||
- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#image-to-image)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.6</b></summary>
|
||||
|
||||
- Added `easy XYInputs: Checkpoint`
|
||||
- Added `easy XYInputs: Lora`
|
||||
- `easy seed` can manually switch the random seed when increasing the fixed seed value
|
||||
- Fixed `easy fullLoader` and all loaders to automatically adjust the node size when switching LoRa
|
||||
- Removed the original ttn image saving logic and adapted to the default image saving format extension of ComfyUI
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.5</b></summary>
|
||||
|
||||
- Added `easy isSDXL`
|
||||
- Added prompt word control on `easy svdLoader`, which can be used with open_clip model
|
||||
- Added **populated_text** on `easy wildcards`, wildcard populated text can be output
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.4</b></summary>
|
||||
|
||||
- `easy showAnything` added support for converting other types (e.g., tensor conditions, images, etc.)
|
||||
- Added `easy showLoaderSettingsNames` can display the model and VAE name in the output loader assembly
|
||||
- Added `easy promptList`
|
||||
- Added `easy fooocusInpaintLoader` (only the process of SDXLModel is supported)
|
||||
- Added **Logic** nodes
|
||||
- Added `easy imageSave` - Image saving node with date conversion and aspect and height formatting
|
||||
- Added `easy joinImageBatch`
|
||||
- `easy kSamplerInpainting` Added the **patch** input value to be used with the FooocusInpaintLoader node
|
||||
|
||||
- Fixed xyplot error when with Pillow>9.5
|
||||
- Fixed `easy wildcards` An error is reported when running with the PS extension
|
||||
- Fixed `easy XYInputs: ControlNet` Error
|
||||
- Fixed `easy loraStack` error when **toggle** is disabled
|
||||
|
||||
|
||||
- Changing the first-time install node package no longer automatically replaces the theme, you need to manually adjust and refresh the page
|
||||
- `easy imageSave` added **only_preivew**
|
||||
- Adjust the `easy latentCompositeMaskedWithCond` node
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.3</b></summary>
|
||||
|
||||
- Added `easy stylesSelector`
|
||||
- Added **scale_soft_weights** in `easy controlnetLoader` and `easy controlnetLoaderADV`
|
||||
- Added the queue progress bar setting item, which is not enabled by default
|
||||
|
||||
|
||||
- Fixed `easy XYInputs: Sampler/Scheduler` Error
|
||||
- Fixed the right menu has a problem when clicking the button
|
||||
- Fixed `easy comfyLoader` error
|
||||
- Fixed xyPlot error when connecting to zero123
|
||||
- Fixed the error message in the loader when the prompt word was component
|
||||
- Fixed `easy getNode` and `easy setNode` the title does not change when loading
|
||||
- Fixed all samplers using subdirectories to store images
|
||||
|
||||
|
||||
- Adjust the UI theme, divided into two sets of styles: the official default background and the dark black background, which can be switched in the color palette in the settings
|
||||
- Modify the styles path to be compatible with other environments
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.2</b></summary>
|
||||
|
||||
- Added `easy XYPlotAdvanced` and some nodes about `easy XYInputs`
|
||||
- Added **Alt+1-Alt+9** Shortcut keys to quickly paste node presets for Node templates (corresponding to 1~9 sequences)
|
||||
- Added a `📜Groups Map(EasyUse)` to the context menu.
|
||||
- An `autocomplete` folder has been added, If you have [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) installed, the txt files in that folder will be merged and overwritten to the autocomplete .txt file of the pyssss package at startup.
|
||||
|
||||
|
||||
- Fixed XYPlot is not working when `a1111_prompt_style` is True
|
||||
- Fixed UI loading failure in the new version of ComfyUI
|
||||
- `easy XYInputs ModelMergeBlocks` Values can be imported from CSV files
|
||||
- Fixed `easy pipeToBasicPipe` Bug
|
||||
|
||||
|
||||
- Removed `easy imageRemBg`
|
||||
- Remove the introductory diagram and workflow files from the package to reduce the package size
|
||||
- Replaced the font file used in the generation of XY diagrams
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.1</b></summary>
|
||||
|
||||
- Fixed `easy comfyLoader` error
|
||||
- Fixed All nodes that contain the value of the image size
|
||||
- Added `easy kSamplerInpainting`
|
||||
- Added `easy pipeToBasicPipe`
|
||||
- Fixed `width` and `height` can not customize in `easy svdLoader`
|
||||
- Fixed all preview image path (Previously, it was not possible to preview the image on the Mac system)
|
||||
- Fixed `vae_name` is not working in `easy fullLoader` and `easy a1111Loader` and `easy comfyLoader`
|
||||
- Fixed `easy fullkSampler` outputs error
|
||||
- Fixed `model_override` is not working in `easy fullLoader`
|
||||
- Fixed `easy hiresFix` error
|
||||
- Fixed `easy xyplot` font file path error
|
||||
- Fixed seed that cannot be fixed when you convert `seed_num` to `easy seed`
|
||||
- Fixed `easy pipeIn` inputs bug
|
||||
- `easy preDetailerFix` have added a new parameter `optional_image`
|
||||
- Fixed `easy zero123Loader` and `easy svdLoader` model into cache.
|
||||
- Added `easy seed`
|
||||
- Fixed `image_output` default value is "Preview"
|
||||
- `easy fullLoader` and `easy a1111Loader` have added a new parameter `a1111_prompt_style`,that can reproduce the same image generated from stable-diffusion-webui on comfyui, but you need to install [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) to use this feature in the current version
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.0</b></summary>
|
||||
|
||||
- Added `easy positive` - simple positive prompt text
|
||||
- Added `easy negative` - simple negative prompt text
|
||||
- Added `easy wildcards` - support for wildcards and hint text selected by Lora
|
||||
- Added `easy portraitMaster` - PortraitMaster v2.2
|
||||
- Added `easy loraStack` - Lora stack
|
||||
- Added `easy fullLoader` - full version of the loader
|
||||
- Added `easy zero123Loader` - simple zero123 loader
|
||||
- Added `easy svdLoader` - easy svd loader
|
||||
- Added `easy fullkSampler` - full version of the sampler (no separation)
|
||||
- Added `easy hiresFix` - support for HD repair of Pipe
|
||||
- Added `easy predetailerFix` and `easy DetailerFix` - support for Pipe detail fixing
|
||||
- Added `easy ultralyticsDetectorPipe` and `easy samLoaderPipe` - Detect loader (detail fixed input)
|
||||
- Added `easy pipein` `easy pipeout` - Pipe input and output
|
||||
- Added `easy xyPlot` - simple xyplot (more controllable parameters will be updated in the future)
|
||||
- Added `easy imageRemoveBG` - image to remove background
|
||||
- Added `easy imagePixelPerfect` - image pixel perfect
|
||||
- Added `easy poseEditor` - Pose editor
|
||||
- New UI Theme (Obsidian) - Auto-load UI by default, which can also be changed in the settings
|
||||
|
||||
- Fixed `easy globalSeed` is not working
|
||||
- Fixed an issue where all `seed_num` values were out of order due to [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) updating the chart in real time
|
||||
- Fixed `easy imageSize`, `easy imageSizeBySide`, `easy imageSizeByLongerSide` as end nodes
|
||||
- Fixed the bug that `seed_num` (random seed value) could not be read consistently in history
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Updated at 12/14/2023</b></summary>
|
||||
|
||||
- `easy a1111Loader` and `easy comfyLoader` added `batch_size` of required input parameters
|
||||
- Added the `easy controlnetLoaderADV` node
|
||||
- `easy controlnetLoaderADV` and `easy controlnetLoader` added `control_net ` of optional input parameters
|
||||
- `easy preSampling` and `easy preSamplingAdvanced` added `image_to_latent` optional input parameters
|
||||
- Added the `easy imageSizeBySide` node, which can be output as a long side or a short side
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Updated at 12/13/2023</b></summary>
|
||||
|
||||
- Added the `easy LLLiteLoader` node, if you have pre-installed the kohya-ss/ControlNet-LLLite-ComfyUI package, please move the model files in the models to `ComfyUI\models\controlnet\` (i.e. in the default controlnet path of comfy, please do not change the file name of the model, otherwise it will not be read).
|
||||
- Modify `easy controlnetLoader` to the bottom of the loader category.
|
||||
- Added size display for `easy imageSize` and `easy imageSizeByLongerSize` outputs.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Updated at 12/11/2023</b></summary>
|
||||
- Added the `showSpentTime` node to display the time spent on image diffusion and the time spent on VAE decoding images
|
||||
</details>
|
||||
|
||||
## The relevant node package involved
|
||||
|
||||
Disclaimer: Opened source was not easy. I have a lot of respect for the contributions of these original authors. I just did some integration and optimization.
|
||||
|
||||
| Nodes Name(Search Name) | Related libraries | Library-related node |
|
||||
|:-------------------------------|:----------------------------------------------------------------------------|:-------------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply... |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
|
||||
| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
|
||||
| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader |
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - Powerful and modular Stable Diffusion GUI
|
||||
|
||||
[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI Manager
|
||||
|
||||
[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - Pipe nodes (node bundles) allow users to reduce unnecessary connections
|
||||
|
||||
[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - Diffus3 gets and sets points that allow the user to detach the composition of the workflow
|
||||
|
||||
[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - General modpack 1
|
||||
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - General Modpack 2
|
||||
|
||||
[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - Make model generation independent of training resolution
|
||||
|
||||
[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - Style migration
|
||||
|
||||
[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - Face migration
|
||||
|
||||
[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - Face migration
|
||||
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss🐍
|
||||
|
||||
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - Image Preview Chooser
|
||||
|
||||
[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT custom nodes
|
||||
|
||||
|
||||
## 🌟Stargazers
|
||||
|
||||
My gratitude extends to the generous souls who bestow a star. Your support is much appreciated!
|
||||
|
||||
[](https://github.com/yolain/ComfyUI-Easy-Use/stargazers)
|
||||
@@ -1,442 +1,471 @@
|
||||
<p align="right">
|
||||
<strong>中文</strong> | <a href="./README.en.md">English</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
# ComfyUI Easy Use
|
||||
|
||||
[](https://www.bilibili.com/video/BV1w6421F7Uv)
|
||||
[](https://www.bilibili.com/video/BV1vQ4y1G7z7/)
|
||||
</div>
|
||||
|
||||
**ComfyUI-Easy-Use** 是一个化繁为简的节点整合包, 在 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的基础上进行延展,并针对了诸多主流的节点包做了整合与优化,以达到更快更方便使用ComfyUI的目的,在保证自由度的同时还原了本属于Stable Diffusion的极致畅快出图体验。
|
||||
|
||||
[](https://github.com/yolain/ComfyUI-Yolain-Workflows)
|
||||
|
||||
## 👨🏻🎨 特色介绍
|
||||
|
||||
- 沿用了 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的思路,大大减少了折腾工作流的时间成本。
|
||||
- UI界面美化,首次安装的用户,如需使用UI主题,请在 Settings -> Color Palette 中自行切换主题并**刷新页面**即可
|
||||
- 增加了预采样参数配置的节点,可与采样节点分离,更方便预览。
|
||||
- 支持通配符与Lora的提示词节点,如需使用Lora Block Weight用法,需先保证自定义节点包中安装了 [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack)
|
||||
- 可多选的风格化提示词选择器,默认是Fooocus的样式json,可自定义json放在styles底下,samples文件夹里可放预览图(名称和name一致,图片文件名如有空格需转为下划线'_')
|
||||
- 加载器可开启A1111提示词风格模式,可重现与webui生成近乎相同的图像,需先安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes)
|
||||
- 可使用`easy latentNoisy`或`easy preSamplingNoiseIn`节点实现对潜空间的噪声注入
|
||||
- 简化 SD1.x、SD2.x、SDXL、SVD、Zero123等流程
|
||||
- 简化 Stable Cascade [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#1-13-stable-cascade)
|
||||
- 简化 Layer Diffuse [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-3-layerdiffusion)
|
||||
- 简化 InstantID [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid), 需先保证自定义节点包中安装了 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID)
|
||||
- 简化 IPAdapter, 需先保证自定义节点包中安装最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus)
|
||||
- 扩展 XYplot 的可用性
|
||||
- 整合了Fooocus Inpaint功能
|
||||
- 整合了常用的逻辑计算、转换类型、展示所有类型等
|
||||
- 支持节点上checkpoint、lora模型子目录分类及预览图 (请在设置中开启上下文菜单嵌套子目录)
|
||||
- 支持BriaAI的RMBG-1.4模型的背景去除节点,[技术参考](https://huggingface.co/briaai/RMBG-1.4)
|
||||
- 支持 强制清理comfyUI模型显存占用
|
||||
- 支持Stable Diffusion 3 多账号API节点
|
||||
- 支持IC-Light的应用 [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-5-ic-light) | [代码整合来源](https://github.com/huchenlei/ComfyUI-IC-Light) | [技术参考](https://github.com/lllyasviel/IC-Light)
|
||||
- 中文提示词自动识别,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en)
|
||||
- 支持 sd3 模型
|
||||
- 支持 kolors 模型
|
||||
|
||||
## 👨🏻🔧 安装
|
||||
|
||||
1. 将存储库克隆到 **custom_nodes** 目录并安装依赖
|
||||
```shell
|
||||
#1. git下载
|
||||
git clone https://github.com/yolain/ComfyUI-Easy-Use
|
||||
#2. 安装依赖
|
||||
双击install.bat安装依赖
|
||||
```
|
||||
|
||||
## ☕️ 计划
|
||||
|
||||
- [ ] 更新便于维护的新前端代码
|
||||
- [x] 使用sass维护css样式
|
||||
- [ ] 对原有扩展进行优化
|
||||
- [ ] 增加新的组件(如节点时间统计等)
|
||||
- [ ] 增加浅色主题
|
||||
- [ ] 在[ComfyUI-Yolain-Workflows](https://github.com/yolain/ComfyUI-Yolain-Workflows)中上传更多的工作流(如kolors,sd3等),并更新english版本的readme
|
||||
- [ ] 更详细功能介绍的 gitbook
|
||||
|
||||
## 📜 更新日志
|
||||
|
||||
**v1.2.1**
|
||||
|
||||
- 增加 `easy ipadapterApplyFaceIDKolors`
|
||||
- `easy ipadapterApply` 和 `easy ipadapterApplyADV` 增加 **PLUS (kolors genernal)** 和 **FACEID PLUS KOLORS** 预置项
|
||||
- `easy imageRemBg` 增加 **inspyrenet** 选项
|
||||
- 增加 `easy controlnetLoader++`
|
||||
- 去除 `easy positive` `easy negative` 等prompt节点的自动将中文翻译功能,自动翻译仅在 `easy a1111Loader` 等不支持中文TE的加载器中生效
|
||||
- 增加 `easy kolorsLoader` - 可灵加载器,参考了 [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) 和 [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper) 的代码。
|
||||
|
||||
**v1.2.0**
|
||||
|
||||
- 增加 `easy pulIDApply` 和 `easy pulIDApplyADV`
|
||||
- 增加 `easy hunyuanDiTLoader` 和 `easy pixArtLoader`
|
||||
- 当新菜单的位置在上或者下时增加上 crystools 的显示,推荐开两个就好(如果后续crystools有更新UI适配我可能会删除掉)
|
||||
- 增加 **easy sliderControl** - 滑块控制节点,当前可用于控制ipadapterMS的参数 (双击滑块可重置为默认值)
|
||||
- 增加 **layer_weights** 属性在 `easy ipadapterApplyADV` 节点
|
||||
|
||||
**v1.1.9**
|
||||
|
||||
- 增加 新的调度器 **gitsScheduler**
|
||||
- 增加 `easy imageBatchToImageList` 和 `easy imageListToImageBatch` (修复Impact版的一点小问题)
|
||||
- 递归模型子目录嵌套
|
||||
- 支持 sd3 模型
|
||||
- 增加 `easy applyInpaint` - 局部重绘全模式节点 (相比与之前的kSamplerInpating节点逻辑会更合理些)
|
||||
|
||||
**v1.1.8**
|
||||
|
||||
- 增加中文提示词自动翻译,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en), 默认已对wildcard、lora正则处理, 其他需要保留的中文,可使用`@你的提示词@`包裹 (若依赖安装完成后报错, 请重启),测算大约会占0.3GB显存
|
||||
- 增加 `easy controlnetStack` - controlnet堆
|
||||
- 增加 `easy applyBrushNet` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json)
|
||||
- 增加 `easy applyPowerPaint` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json)
|
||||
|
||||
**v1.1.7**
|
||||
|
||||
- 修复 一些模型(如controlnet模型等)未成功写入缓存,导致修改前置节点束参数(如提示词)需要二次载入模型的问题
|
||||
- 增加 `easy prompt` - 主体和光影预置项,后期可能会调整
|
||||
- 增加 `easy icLightApply` - 重绘光影, 从[ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)优化
|
||||
- 增加 `easy imageSplitGrid` - 图像网格拆分
|
||||
- `easy kSamplerInpainting` 的 **additional** 属性增加差异扩散和brushnet等相关选项
|
||||
- 增加 brushnet模型加载的支持 - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
|
||||
- 增加 `easy applyFooocusInpaint` - Fooocus内补节点 替代原有的 FooocusInpaintLoader
|
||||
- 移除 `easy fooocusInpaintLoader` - 容易bug,不再使用
|
||||
- 修改 easy kSampler等采样器中并联的model 不再替换输出中pipe里的model
|
||||
|
||||
**v1.1.6**
|
||||
|
||||
- 增加步调齐整适配 - 在所有的预采样和全采样器节点中的 调度器(schedulder) 增加了 **alignYourSteps** 选项
|
||||
- `easy kSampler` 和 `easy fullkSampler` 的 **image_output** 增加 **Preview&Choose**选项
|
||||
- 增加 `easy styleAlignedBatchAlign` - 风格对齐 [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy)
|
||||
- 增加 `easy ckptNames`
|
||||
- 增加 `easy controlnetNames`
|
||||
- 增加 `easy imagesSplitimage` - 批次图像拆分单张
|
||||
- 增加 `easy imageCount` - 图像数量
|
||||
- 增加 `easy textSwitch` - 文字切换
|
||||
|
||||
**v1.1.5**
|
||||
|
||||
- 重写 `easy cleanGPUUsed` - 可强制清理comfyUI的模型显存占用
|
||||
- 增加 `easy humanSegmentation` - 多类分割、人像分割
|
||||
- 增加 `easy imageColorMatch`
|
||||
- 增加 `easy ipadapterApplyRegional`
|
||||
- 增加 `easy ipadapterApplyFromParams`
|
||||
- 增加 `easy imageInterrogator` - 图像反推
|
||||
- 增加 `easy stableDiffusion3API` - 简易的Stable Diffusion 3 多账号API节点
|
||||
|
||||
**v1.1.4**
|
||||
|
||||
- 增加 `easy imageChooser` - 从[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker)简化的图片选择器
|
||||
- 增加 `easy preSamplingCustom` - 自定义预采样,可支持cosXL-edit
|
||||
- 增加 `easy ipadapterStyleComposition`
|
||||
- 增加 在Loaders上右键菜单可查看 checkpoints、lora 信息
|
||||
- 修复 `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` 以兼容ComfyUI Revision>=2098 [0542088e] 以上版本
|
||||
- 修复 FooocusInpaint修改ModelPatcher计算权重引发的问题,理应在生成model后重置ModelPatcher为默认值
|
||||
|
||||
**v1.1.3**
|
||||
|
||||
- `easy ipadapterApply` 增加 **COMPOSITION** 预置项
|
||||
- 增加 对[ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) lora模型 的加载支持
|
||||
- 增加 `easy promptLine`
|
||||
- 增加 `easy promptReplace`
|
||||
- 增加 `easy promptConcat`
|
||||
- `easy wildcards` 增加 **multiline_mode**属性
|
||||
- 增加 当节点需要下载模型时,若huggingface连接超时,会切换至镜像地址下载模型
|
||||
|
||||
**v1.1.2**
|
||||
|
||||
- 改写 EasyUse 相关节点的部分插槽推荐节点
|
||||
- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图
|
||||
- 增加 `easy sv3dLoader`
|
||||
- 增加 `easy dynamiCrafterLoader`
|
||||
- 增加 `easy ipadapterApply`
|
||||
- 增加 `easy ipadapterApplyADV`
|
||||
- 增加 `easy ipadapterApplyEncoder`
|
||||
- 增加 `easy ipadapterApplyEmbeds`
|
||||
- 增加 `easy preMaskDetailerFix`
|
||||
- `easy kSamplerInpainting` 增加 **additional** 属性,可设置成 Differential Diffusion 或 Only InpaintModelConditioning
|
||||
- 修复 `easy stylesSelector` 当未选择样式时,原有提示词发生了变化
|
||||
- 修复 `easy pipeEdit` 提示词输入lora时报错
|
||||
- 修复 layerDiffuse xyplot相关bug
|
||||
|
||||
**v1.1.1**
|
||||
|
||||
- 修复首次添加含seed的节点且当前模式为control_before_generate时,seed为0的问题
|
||||
- `easy preSamplingAdvanced` 增加 **return_with_leftover_noise**
|
||||
- 修复 `easy stylesSelector` 当选择自定义样式文件时运行队列报错
|
||||
- `easy preSamplingLayerDiffusion` 增加 mask 可选传入参数
|
||||
- 将所有 **seed_num** 调整回 **seed**
|
||||
- 修补官方BUG: 当control_mode为before 在首次加载页面时未修改节点中widget名称为 control_before_generate
|
||||
- 去除强制**control_before_generate**设定
|
||||
- 增加 `easy imageRemBg` - 默认为BriaAI的RMBG-1.4模型, 移除背景效果更加,速度更快
|
||||
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.0</b></summary>
|
||||
|
||||
- 增加 `easy imageSplitList` - 拆分每 N 张图像
|
||||
- 增加 `easy preSamplingDiffusionADDTL` - 可配置前景、背景、blended的additional_prompt等
|
||||
- 增加 `easy preSamplingNoiseIn` 可替代需要前置的`easy latentNoisy`节点 实现效果更好的噪声注入
|
||||
- `easy pipeEdit` 增加 条件拼接模式选择,可选择替换、合并、联结、平均、设置条件时间
|
||||
- 增加 `easy pipeEdit` - 可编辑Pipe的节点(包含可重新输入提示词)
|
||||
- 增加 `easy preSamplingLayerDiffusion` 与 `easy kSamplerLayerDiffusion` (连接 `easy kSampler` 也能通)
|
||||
- 增加 在 加载器、预采样、采样器、Controlnet等节点上右键可快速替换同类型节点的便捷菜单
|
||||
- 增加 `easy instantIDApplyADV` 可连入 positive 与 negative
|
||||
- 修复 `easy wildcards` 读取lora未填写完整路径时未自动检索导致加载lora失败的问题
|
||||
- 修复 `easy instantIDApply` mask 未传入正确值
|
||||
- 修复 在 非a1111提示词风格下 BREAK 不生效的问题
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.9</b></summary>
|
||||
|
||||
- 修复未安装 ComfyUI-Impack-Pack 和 ComfyUI_InstantID 时报错
|
||||
- 修复 `easy pipeIn` - pipe设为可不必选
|
||||
- 增加 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid)
|
||||
- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表
|
||||
- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.8</b></summary>
|
||||
|
||||
- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints))
|
||||
- `easy styleSelector` 搜索框修改为不区分大小写匹配
|
||||
- `easy fullLoader` 增加 **positive**、**negative**、**latent** 输出项
|
||||
- 修复 SDXLClipModel 在 ComfyUI 修订版本号 2016[c2cb8e88] 及以上的报错(判断了版本号可兼容老版本)
|
||||
- 修复 `easy detailerFix` 批次大小大于1时生成出错
|
||||
- 修复`easy preSampling`等 latent传入后无法根据批次索引生成的问题
|
||||
- 修复 `easy svdLoader` 报错
|
||||
- 优化代码,减少了诸多冗余,提升运行速度
|
||||
- 去除中文翻译对照文本
|
||||
|
||||
(翻译对照已由 [AIGODLIKE-COMFYUI-TRANSLATION](https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation) 统一维护啦!
|
||||
首次下载或者版本较早的朋友请更新 AIGODLIKE-COMFYUI-TRANSLATION 和本节点包至最新版本。)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.7</b></summary>
|
||||
|
||||
- 增加 `easy cascadeLoader` - stable cascade 加载器
|
||||
- 增加 `easy preSamplingCascade` - stabled cascade stage_c 预采样参数
|
||||
- 增加 `easy fullCascadeKSampler` - stable cascade stage_c 完整版采样器
|
||||
- 增加 `easy cascadeKSampler` - stable cascade stage-c ksampler simple
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.6</b></summary>
|
||||
|
||||
- 增加 `easy XYInputs: Checkpoint`
|
||||
- 增加 `easy XYInputs: Lora`
|
||||
- `easy seed` 增加固定种子值时可手动切换随机种
|
||||
- 修复 `easy fullLoader`等加载器切换lora时自动调整节点大小的问题
|
||||
- 去除原有ttn的图片保存逻辑并适配ComfyUI默认的图片保存格式化扩展
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.5</b></summary>
|
||||
|
||||
- 增加 `easy isSDXL`
|
||||
- `easy svdLoader` 增加提示词控制, 可配合open_clip模型进行使用
|
||||
- `easy wildcards` 增加 **populated_text** 可输出通配填充后文本
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.4</b></summary>
|
||||
|
||||
- 增加 `easy showLoaderSettingsNames` 可显示与输出加载器部件中的 模型与VAE名称
|
||||
- 增加 `easy promptList` - 提示词列表
|
||||
- 增加 `easy fooocusInpaintLoader` - Fooocus内补节点(仅支持XL模型的流程)
|
||||
- 增加 **Logic** 逻辑类节点 - 包含类型、计算、判断和转换类型等
|
||||
- 增加 `easy imageSave` - 带日期转换和宽高格式化的图像保存节点
|
||||
- 增加 `easy joinImageBatch` - 合并图像批次
|
||||
- `easy showAnything` 增加支持转换其他类型(如:tensor类型的条件、图像等)
|
||||
- `easy kSamplerInpainting` 增加 **patch** 传入值,配合Fooocus内补节点使用
|
||||
- `easy imageSave` 增加 **only_preivew**
|
||||
|
||||
- 修复 xyplot在pillow>9.5中报错
|
||||
- 修复 `easy wildcards` 在使用PS扩展插件运行时报错
|
||||
- 修复 `easy latentCompositeMaskedWithCond`
|
||||
- 修复 `easy XYInputs: ControlNet` 报错
|
||||
- 修复 `easy loraStack` **toggle** 为 disabled 时报错
|
||||
|
||||
- 修改首次安装节点包不再自动替换主题,需手动调整并刷新页面
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.3</b></summary>
|
||||
|
||||
- 增加 `easy stylesSelector` 风格化提示词选择器
|
||||
- 增加队列进度条设置项,默认为未启用状态
|
||||
- `easy controlnetLoader` 和 `easy controlnetLoaderADV` 增加参数 **scale_soft_weights**
|
||||
|
||||
|
||||
- 修复 `easy XYInputs: Sampler/Scheduler` 报错
|
||||
- 修复 右侧菜单 点击按钮时老是跑位的问题
|
||||
- 修复 styles 路径在其他环境报错
|
||||
- 修复 `easy comfyLoader` 读取错误
|
||||
- 修复 xyPlot 在连接 zero123 时报错
|
||||
- 修复加载器中提示词为组件时报错
|
||||
- 修复 `easy getNode` 和 `easy setNode` 加载时标题未更改
|
||||
- 修复所有采样器中存储图片使用子目录前缀不生效的问题
|
||||
|
||||
|
||||
- 调整UI主题
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.2</b></summary>
|
||||
|
||||
- 增加 **autocomplete** 文件夹,如果您安装了 [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts), 将在启动时合并该文件夹下的所有txt文件并覆盖到pyssss包里的autocomplete.txt文件。
|
||||
- 增加 `easy XYPlotAdvanced` 和 `easy XYInputs` 等相关节点
|
||||
- 增加 **Alt+1到9** 快捷键,可快速粘贴 Node templates 的节点预设 (对应 1到9 顺序)
|
||||
|
||||
- 修复 `easy imageInsetCrop` 测量值为百分比时步进为1
|
||||
- 修复 开启 `a1111_prompt_style` 时XY图表无法使用的问题
|
||||
- 右键菜单中增加了一个 `📜Groups Map(EasyUse)`
|
||||
|
||||
- 修复在Comfy新版本中UI加载失败
|
||||
- 修复 `easy pipeToBasicPipe` 报错
|
||||
- 修改 `easy fullLoader` 和 `easy a1111Loader` 中的 **a1111_prompt_style** 默认值为 False
|
||||
- `easy XYInputs ModelMergeBlocks` 支持csv文件导入数值
|
||||
|
||||
- 替换了XY图生成时的字体文件
|
||||
|
||||
- 移除 `easy imageRemBg`
|
||||
- 移除包中的介绍图和工作流文件,减少包体积
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.1</b></summary>
|
||||
|
||||
- 新增 `easy seed` - 简易随机种
|
||||
- `easy preDetailerFix` 新增了 `optional_image` 传入图像可选,如未传默认取值为pipe里的图像
|
||||
- 新增 `easy kSamplerInpainting` 用于内补潜空间的采样器
|
||||
- 新增 `easy pipeToBasicPipe` 用于转换到Impact的某些节点上
|
||||
|
||||
- 修复 `easy comfyLoader` 报错
|
||||
- 修复所有包含输出图片尺寸的节点取值方式无法批处理的问题
|
||||
- 修复 `width` 和 `height` 无法在 `easy svdLoader` 自定义的报错问题
|
||||
- 修复所有采样器预览图片的地址链接 (解决在 MACOS 系统中图片无法在采样器中预览的问题)
|
||||
- 修复 `vae_name` 在 `easy fullLoader` 和 `easy a1111Loader` 和 `easy comfyLoader` 中选择但未替换原始vae问题
|
||||
- 修复 `easy fullkSampler` 除pipe外其他输出值的报错
|
||||
- 修复 `easy hiresFix` 输入连接pipe和image、vae同时存在时报错
|
||||
- 修复 `easy fullLoader` 中 `model_override` 连接后未执行
|
||||
- 修复 因新增`easy seed` 导致action错误
|
||||
- 修复 `easy xyplot` 的字体文件路径读取错误
|
||||
- 修复 convert 到 `easy seed` 随机种无法固定的问题
|
||||
- 修复 `easy pipeIn` 值传入的报错问题
|
||||
- 修复 `easy zero123Loader` 和 `easy svdLoader` 读取模型时将模型加入到缓存中
|
||||
- 修复 `easy kSampler` `easy kSamplerTiled` `easy detailerFix` 的 `image_output` 默认值为 Preview
|
||||
- `easy fullLoader` 和 `easy a1111Loader` 新增了 `a1111_prompt_style` 参数可以重现和webui生成相同的图像,当前您需要安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) 才能使用此功能
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.0</b></summary>
|
||||
|
||||
- 新增`easy positive` - 简易正面提示词文本
|
||||
- 新增`easy negative` - 简易负面提示词文本
|
||||
- 新增`easy wildcards` - 支持通配符和Lora选择的提示词文本
|
||||
- 新增`easy portraitMaster` - 肖像大师v2.2
|
||||
- 新增`easy loraStack` - Lora堆
|
||||
- 新增`easy fullLoader` - 完整版的加载器
|
||||
- 新增`easy zero123Loader` - 简易zero123加载器
|
||||
- 新增`easy svdLoader` - 简易svd加载器
|
||||
- 新增`easy fullkSampler` - 完整版的采样器(无分离)
|
||||
- 新增`easy hiresFix` - 支持Pipe的高清修复
|
||||
- 新增`easy predetailerFix` `easy DetailerFix` - 支持Pipe的细节修复
|
||||
- 新增`easy ultralyticsDetectorPipe` `easy samLoaderPipe` - 检测加载器(细节修复的输入项)
|
||||
- 新增`easy pipein` `easy pipeout` - Pipe的输入与输出
|
||||
- 新增`easy xyPlot` - 简易的xyplot (后续会更新更多可控参数)
|
||||
- 新增`easy imageRemoveBG` - 图像去除背景
|
||||
- 新增`easy imagePixelPerfect` - 图像完美像素
|
||||
- 新增`easy poseEditor` - 姿势编辑器
|
||||
- 新增UI主题(黑曜石)- 默认自动加载UI, 也可在设置中自行更替
|
||||
|
||||
- 修复 `easy globalSeed` 不生效问题
|
||||
- 修复所有的`seed_num` 因 [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) 实时更新图表导致值错乱的问题
|
||||
- 修复`easy imageSize` `easy imageSizeBySide` `easy imageSizeByLongerSide` 可作为终节点
|
||||
- 修复 `seed_num` (随机种子值) 在历史记录中读取无法一致的Bug
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary><b>v0.5</b></summary>
|
||||
|
||||
- 新增 `easy controlnetLoaderADV` 节点
|
||||
- 新增 `easy imageSizeBySide` 节点,可选输出为长边或短边
|
||||
- 新增 `easy LLLiteLoader` 节点,如果您预先安装过 kohya-ss/ControlNet-LLLite-ComfyUI 包,请将 models 里的模型文件移动至 ComfyUI\models\controlnet\ (即comfy默认的controlnet路径里,请勿修改模型的文件名,不然会读取不到)。
|
||||
- 新增 `easy imageSize` 和 `easy imageSizeByLongerSize` 输出的尺寸显示。
|
||||
- 新增 `easy showSpentTime` 节点用于展示图片推理花费时间与VAE解码花费时间。
|
||||
- `easy controlnetLoaderADV` 和 `easy controlnetLoader` 新增 `control_net` 可选传入参数
|
||||
- `easy preSampling` 和 `easy preSamplingAdvanced` 新增 `image_to_latent` 可选传入参数
|
||||
- `easy a1111Loader` 和 `easy comfyLoader` 新增 `batch_size` 传入参数
|
||||
|
||||
- 修改 `easy controlnetLoader` 到 loader 分类底下。
|
||||
</details>
|
||||
|
||||
## 整合参考到的相关节点包
|
||||
|
||||
声明: 非常尊重这些原作者们的付出,开源不易,我仅仅只是做了一些整合与优化。
|
||||
|
||||
| 节点名 (搜索名) | 相关的库 | 库相关的节点 |
|
||||
|:-------------------------------|:----------------------------------------------------------------------------|:------------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy if | [ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) | IfExecute |
|
||||
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
|
||||
| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
|
||||
| easy icLightApply | [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light) | ICLightApply等 |
|
||||
| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader |
|
||||
|
||||
## Credits
|
||||
|
||||
[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - 功能强大且模块化的Stable Diffusion GUI
|
||||
|
||||
[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI管理器
|
||||
|
||||
[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - 管道节点(节点束)让用户减少了不必要的连接
|
||||
|
||||
[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - diffus3的获取与设置点让用户可以分离工作流构成
|
||||
|
||||
[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - 常规整合包1
|
||||
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - 常规整合包2
|
||||
|
||||
[ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) - ComfyUI逻辑运算
|
||||
|
||||
[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - 让模型生成不受训练分辨率限制
|
||||
|
||||
[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - 风格迁移
|
||||
|
||||
[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - 人脸迁移
|
||||
|
||||
[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - 人脸迁移
|
||||
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss 小蛇🐍脚本
|
||||
|
||||
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - 图片选择器
|
||||
|
||||
[ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) - BrushNet 内补节点
|
||||
|
||||
[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT架构相关节点(Pixart、混元DiT等)
|
||||
|
||||
## 🌟Stargazers
|
||||
|
||||
My gratitude extends to the generous souls who bestow a star. Your support is much appreciated!
|
||||
|
||||

|
||||
|
||||
<div align="center">
|
||||
<a href="https://space.bilibili.com/1840885116">Video Tutorial</a> |
|
||||
Docs (Cooming Soon) |
|
||||
<a href="https://github.com/yolain/ComfyUI-Yolain-Workflows">Workflow Collection</a> |
|
||||
<a href="#%EF%B8%8F-donation">Donation</a>
|
||||
<br><br>
|
||||
<a href="./README.md"><img src="https://img.shields.io/badge/🇬🇧English-0b8cf5"></a>
|
||||
<a href="./README.ZH_CN.md"><img src="https://img.shields.io/badge/🇨🇳中文简体-e9e9e9"></a>
|
||||
</div>
|
||||
|
||||
**ComfyUI-Easy-Use** is an efficiency custom nodes integration package, which is extended on the basis of [TinyTerraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes). It has been integrated and optimized for many popular awesome custom nodes to achieve the purpose of faster and more convenient use of ComfyUI. While ensuring the degree of freedom, it restores the ultimate smooth image production experience that belongs to Stable Diffusion.
|
||||
|
||||
## 👨🏻🎨 Introduce
|
||||
|
||||
- Inspire by [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes), which greatly reduces the time cost of tossing workflows。
|
||||
- UI interface beautification, the first time you install the user, if you need to use the UI theme, please switch the theme in Settings -> Color Palette and refresh page.
|
||||
- Added a node for pre-sampling parameter configuration, which can be separated from the sampling node for easier previewing
|
||||
- Wildcards and lora's are supported, for Lora Block Weight usage, ensure that the custom node package has the [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack)
|
||||
- Multi-selectable styled cue word selector, default is Fooocus style json, custom json can be placed under styles, samples folder can be placed in the preview image (name and name consistent, image file name such as spaces need to be converted to underscores '_')
|
||||
- The loader enables the A1111 prompt mode, which reproduces nearly identical images to those generated by webui, and needs to be installed [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) first.
|
||||
- Noise injection into the latent space can be achieved using the `easy latentNoisy` or `easy preSamplingNoiseIn` node
|
||||
- Simplified processes for SD1.x, SD2.x, SDXL, SVD, Zero123, etc. [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableDiffusion)
|
||||
- Simplified Stable Cascade [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
|
||||
- Simplified Layer Diffuse [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#LayerDiffusion),The first time you use it you may need to run `pip install -r requirements.txt` to install the required dependencies.
|
||||
- Simplified InstantID [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID), You need to make sure that the custom node package has the [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID)
|
||||
- Extending the usability of XYplot
|
||||
- Fooocus Inpaint integration
|
||||
- Integration of common logical calculations, conversion of types, display of all types, etc.
|
||||
- Background removal nodes for the RMBG-1.4 model supporting BriaAI, [BriaAI Guide](https://huggingface.co/briaai/RMBG-1.4)
|
||||
- Forcibly cleared the memory usage of the comfy UI model are supported
|
||||
- Stable Diffusion 3 multi-account API nodes are supported
|
||||
- Support SD3's model
|
||||
- Support Kolors‘s model
|
||||
- Support Flux's model
|
||||
- Support lazy if else and for loops
|
||||
|
||||
## 👨🏻🔧 Installation
|
||||
Clone the repo into the **custom_nodes** directory and install the requirements:
|
||||
```shell
|
||||
#1. Clone the repo
|
||||
git clone https://github.com/yolain/ComfyUI-Easy-Use
|
||||
#2. Install the requirements
|
||||
Double-click install.bat to install the required dependencies
|
||||
```
|
||||
|
||||
## 📜 Changelog
|
||||
|
||||
**v1.2.5**
|
||||
|
||||
- Added `enable (GPU=A1111)` noise mode on `easy preSamplingCustom` and `easy preSamplingAdvanced`
|
||||
- Added `easy makeImageForICLora`
|
||||
- Added `REGULAR - FLUX and SD3.5 only (high strength)` preset for InstantX Flux ipadapter on `easy ipadapterApply`
|
||||
- Fix brushnet can not be used with startup arg `--fast` mode
|
||||
- Support briaai RMBG-2.0
|
||||
- Support mochi
|
||||
- Implement reuse of end nodes output in the loop body (e.g: previewImage and showAnything and sth.)
|
||||
|
||||
**v1.2.4**
|
||||
|
||||
- Added `easy imageSplitTiles` and `easy imageTilesFromBatch`
|
||||
- Support `model_override`,`vae_override`,`clip_override` can be input separately to `easy fullLoader`
|
||||
- Added `easy saveImageLazy`
|
||||
- Added `easy loadImageForLoop`
|
||||
- Added `easy isFileExist`
|
||||
- Added `easy saveText`
|
||||
|
||||
**v1.2.3**
|
||||
|
||||
- `easy showAnything` and `easy cleanGPUUsed` added slot of output
|
||||
- Added human parts segmentation to `easy humanSegmentation` - Code based on [ComfyUI_Human_Parts](https://github.com/metal3d/ComfyUI_Human_Parts)
|
||||
- Using FluxGuidance when you are using a flux model and choose basicGuider and set the cfg>0 on `easy preSamplingCustom`
|
||||
- Added `easy loraStackApply` and `easy controlnetStackApply` - Apply loraStack and controlnetStack
|
||||
|
||||
**v1.2.2**
|
||||
|
||||
- Added `easy batchAny`
|
||||
- Added `easy anythingIndexSwitch`
|
||||
- Added `easy forLoopStart` and `easy forLoopEnd`
|
||||
- Added `easy ifElse`
|
||||
- Added v2 web frond-end code
|
||||
- Added `easy fluxLoader`
|
||||
- Added support for `controlnetApply` Related nodes with SD3 and hunyuanDiT
|
||||
- Fixed after using `easy applyFooocusInpaint`, all lora models become unusable
|
||||
|
||||
**v1.2.1**
|
||||
|
||||
- Added `easy ipadapterApplyFaceIDKolors`
|
||||
- Added **inspyrenet** to `easy imageRemBg`
|
||||
- Added `easy controlnetLoader++`
|
||||
- Added **PLUS (kolors genernal)** and **FACEID PLUS KOLORS** preset to `easy ipadapterApply` and `easy ipadapterApplyADV` (Supported kolors ipadapter)
|
||||
- Added `easy kolorsLoader` - Code based on [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ)'s and [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper)'s repo, thanks for their contribution.
|
||||
|
||||
**v1.2.0**
|
||||
|
||||
- Added `easy pulIDApply` and `easy pulIDApplyADV`
|
||||
- Added `easy huanyuanDiTLoader` and `easy pixArtLoader`
|
||||
- Added **easy sliderControl** - Slider control node, which can currently be used to control the parameters of ipadapterMS (double-click the slider to reset to default)
|
||||
- Added **layer_weights** in `easy ipadapterApplyADV`
|
||||
|
||||
**v1.1.9**
|
||||
|
||||
- Added **gitsScheduler**
|
||||
- Added `easy imageBatchToImageList` and `easy imageListToImageBatch`
|
||||
- Recursive subcategories nested for models
|
||||
- Support for Stable Diffusion 3 model
|
||||
- Added `easy applyInpaint` - All inpainting mode in this node
|
||||
|
||||
**v1.1.8**
|
||||
|
||||
- Added `easy controlnetStack`
|
||||
- Added `easy applyBrushNet` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json)
|
||||
- Added `easy applyPowerPaint` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json)
|
||||
|
||||
**v1.1.7**
|
||||
|
||||
- Added `easy prompt` - Subject and light presets, maybe adjusted later
|
||||
- Added `easy icLightApply` - Light and shadow migration, Code based on [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)
|
||||
- Added `easy imageSplitGrid`
|
||||
- `easy kSamplerInpainting` added options such as different diffusion and brushnet in **additional** widget
|
||||
- Support for brushnet model loading - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
|
||||
- Added `easy applyFooocusInpaint` - Replace FooocusInpaintLoader
|
||||
- Removed `easy fooocusInpaintLoader`
|
||||
|
||||
**v1.1.6**
|
||||
|
||||
- Added **alignYourSteps** to **schedulder** widget in all `easy preSampling` and `easy fullkSampler`
|
||||
- Added **Preview&Choose** to **image_output** widget in `easy kSampler` & `easy fullkSampler`
|
||||
- Added `easy styleAlignedBatchAlign` - Credit of [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy)
|
||||
- Added `easy ckptNames`
|
||||
- Added `easy controlnetNames`
|
||||
- Added `easy imagesSplitimage` - Batch images split into single images
|
||||
- Added `easy imageCount` - Get Image Count
|
||||
- Added `easy textSwitch` - Text Switch
|
||||
|
||||
**v1.1.5**
|
||||
|
||||
- Rewrite `easy cleanGPUUsed` - the memory usage of the comfyUI can to be cleared
|
||||
- Added `easy humanSegmentation` - Human Part Segmentation
|
||||
- Added `easy imageColorMatch`
|
||||
- Added `easy ipadapterApplyRegional`
|
||||
- Added `easy ipadapterApplyFromParams`
|
||||
- Added `easy imageInterrogator` - Image To Prompt
|
||||
- Added `easy stableDiffusion3API` - Easy Stable Diffusion 3 Multiple accounts API Node
|
||||
|
||||
**v1.1.4**
|
||||
|
||||
- Added `easy preSamplingCustom` - Custom-PreSampling, can be supported cosXL-edit
|
||||
- Added `easy ipadapterStyleComposition`
|
||||
- Added the right-click menu to view checkpoints and lora information in all Loaders
|
||||
- Fixed `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` compatible with ComfyUI Revision>=2098 [0542088e] or later
|
||||
|
||||
|
||||
**v1.1.3**
|
||||
|
||||
- `easy ipadapterApply` Added **COMPOSITION** preset
|
||||
- Supported [ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) when load ResAdapter lora
|
||||
- Added `easy promptLine`
|
||||
- Added `easy promptReplace`
|
||||
- Added `easy promptConcat`
|
||||
- `easy wildcards` Added **multiline_mode**
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.2</b></summary>
|
||||
|
||||
- Optimized some of the recommended nodes for slots related to EasyUse
|
||||
- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews
|
||||
- Added `easy sv3dLoader`
|
||||
- Added `easy dynamiCrafterLoader`
|
||||
- Added `easy ipadapterApply`
|
||||
- Added `easy ipadapterApplyADV`
|
||||
- Added `easy ipadapterApplyEncoder`
|
||||
- Added `easy ipadapterApplyEmbeds`
|
||||
- Added `easy preMaskDetailerFix`
|
||||
- Fixed `easy stylesSelector` is change the prompt when not select the style
|
||||
- Fixed `easy pipeEdit` error when add lora to prompt
|
||||
- Fixed layerDiffuse xyplot bug
|
||||
- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning'
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.1</b></summary>
|
||||
|
||||
- The issue that the seed is 0 when a node with a seed control is added and **control before generate** is fixed for the first time run queue prompt.
|
||||
- `easy preSamplingAdvanced` Added **return_with_leftover_noise**
|
||||
- Fixed `easy stylesSelector` error when choose the custom file
|
||||
- `easy preSamplingLayerDiffusion` Added optional input parameter for mask
|
||||
- Renamed all nodes widget name named seed_num to seed
|
||||
- Remove forced **control_before_generate** settings。 If you want to use control_before_generate, change widget_value_control_mode to before in system settings
|
||||
- Added `easy imageRemBg` - The default is BriaAI's RMBG-1.4 model, which removes the background effect more and faster
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.1.0</b></summary>
|
||||
|
||||
- Added `easy imageSplitList` - to split every N images
|
||||
- Added `easy preSamplingDiffusionADDTL` - It can modify foreground、background or blended additional prompt
|
||||
- Added `easy preSamplingNoiseIn` It can replace the `easy latentNoisy` node that needs to be fronted to achieve better noise injection
|
||||
- `easy pipeEdit` Added conditioning splicing mode selection, you can choose to replace, concat, combine, average, and set timestep range
|
||||
- Added `easy pipeEdit` - nodes that can edit pipes (including re-enterable prompts)
|
||||
- Added `easy preSamplingLayerDiffusion` and `easy kSamplerLayerDiffusion`
|
||||
- Added a convenient menu to right-click on nodes such as Loader, Presampler, Sampler, Controlnet, etc. to quickly replace nodes of the same type
|
||||
- Added `easy instantIDApplyADV` can link positive and negative
|
||||
- Fixed layerDiffusion error when batch size greater than 1
|
||||
- Fixed `easy wildcards` When LoRa is not filled in completely, LoRa is not automatically retrieved, resulting in failure to load LoRa
|
||||
- Fixed the issue that 'BREAK' non-initiation when didn't use a1111 prompt style
|
||||
- Fixed `easy instantIDApply` mask not input right
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.9</b></summary>
|
||||
|
||||
- Fixed the error when ComfyUI-Impack-Pack and ComfyUI_InstantID were not installed
|
||||
- Fixed `easy pipeIn`
|
||||
- Added `easy instantIDApply` - you need installed [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) fisrt, Workflow[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#InstantID)
|
||||
- Fixed `easy detailerFix` not added to the list of nodes available for saving images formatting extensions
|
||||
- Fixed `easy XYInputs: PromptSR` errors are reported when replacing negative prompts
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.8</b></summary>
|
||||
|
||||
- `easy cascadeLoader` stage_c and stage_b support the checkpoint model (Download [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models)
|
||||
- `easy styleSelector` The search box is modified to be case-insensitive
|
||||
- `easy fullLoader` **positive**、**negative**、**latent** added to the output items
|
||||
- Fixed the issue that 'easy preSampling' and other similar node, latent could not be generated based on the batch index after passing in
|
||||
- Fixed `easy svdLoader` error when the positive or negative is empty
|
||||
- Fixed the error of SDXLClipModel in ComfyUI revision 2016[c2cb8e88] and above (the revision number was judged to be compatible with the old revision)
|
||||
- Fixed `easy detailerFix` generation error when batch size is greater than 1
|
||||
- Optimize the code, reduce a lot of redundant code and improve the running speed
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.7</b></summary>
|
||||
|
||||
- Added `easy cascadeLoader` - stable cascade Loader
|
||||
- Added `easy preSamplingCascade` - stable cascade preSampling Settings
|
||||
- Added `easy fullCascadeKSampler` - stable cascade stage-c ksampler full
|
||||
- Added `easy cascadeKSampler` - stable cascade stage-c ksampler simple
|
||||
-
|
||||
- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#image-to-image)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.6</b></summary>
|
||||
|
||||
- Added `easy XYInputs: Checkpoint`
|
||||
- Added `easy XYInputs: Lora`
|
||||
- `easy seed` can manually switch the random seed when increasing the fixed seed value
|
||||
- Fixed `easy fullLoader` and all loaders to automatically adjust the node size when switching LoRa
|
||||
- Removed the original ttn image saving logic and adapted to the default image saving format extension of ComfyUI
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.5</b></summary>
|
||||
|
||||
- Added `easy isSDXL`
|
||||
- Added prompt word control on `easy svdLoader`, which can be used with open_clip model
|
||||
- Added **populated_text** on `easy wildcards`, wildcard populated text can be output
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.4</b></summary>
|
||||
|
||||
- `easy showAnything` added support for converting other types (e.g., tensor conditions, images, etc.)
|
||||
- Added `easy showLoaderSettingsNames` can display the model and VAE name in the output loader assembly
|
||||
- Added `easy promptList`
|
||||
- Added `easy fooocusInpaintLoader` (only the process of SDXLModel is supported)
|
||||
- Added **Logic** nodes
|
||||
- Added `easy imageSave` - Image saving node with date conversion and aspect and height formatting
|
||||
- Added `easy joinImageBatch`
|
||||
- `easy kSamplerInpainting` Added the **patch** input value to be used with the FooocusInpaintLoader node
|
||||
|
||||
- Fixed xyplot error when with Pillow>9.5
|
||||
- Fixed `easy wildcards` An error is reported when running with the PS extension
|
||||
- Fixed `easy XYInputs: ControlNet` Error
|
||||
- Fixed `easy loraStack` error when **toggle** is disabled
|
||||
|
||||
|
||||
- Changing the first-time install node package no longer automatically replaces the theme, you need to manually adjust and refresh the page
|
||||
- `easy imageSave` added **only_preivew**
|
||||
- Adjust the `easy latentCompositeMaskedWithCond` node
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.3</b></summary>
|
||||
|
||||
- Added `easy stylesSelector`
|
||||
- Added **scale_soft_weights** in `easy controlnetLoader` and `easy controlnetLoaderADV`
|
||||
- Added the queue progress bar setting item, which is not enabled by default
|
||||
|
||||
|
||||
- Fixed `easy XYInputs: Sampler/Scheduler` Error
|
||||
- Fixed the right menu has a problem when clicking the button
|
||||
- Fixed `easy comfyLoader` error
|
||||
- Fixed xyPlot error when connecting to zero123
|
||||
- Fixed the error message in the loader when the prompt word was component
|
||||
- Fixed `easy getNode` and `easy setNode` the title does not change when loading
|
||||
- Fixed all samplers using subdirectories to store images
|
||||
|
||||
|
||||
- Adjust the UI theme, divided into two sets of styles: the official default background and the dark black background, which can be switched in the color palette in the settings
|
||||
- Modify the styles path to be compatible with other environments
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.2</b></summary>
|
||||
|
||||
- Added `easy XYPlotAdvanced` and some nodes about `easy XYInputs`
|
||||
- Added **Alt+1-Alt+9** Shortcut keys to quickly paste node presets for Node templates (corresponding to 1~9 sequences)
|
||||
- Added a `📜Groups Map(EasyUse)` to the context menu.
|
||||
- An `autocomplete` folder has been added, If you have [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) installed, the txt files in that folder will be merged and overwritten to the autocomplete .txt file of the pyssss package at startup.
|
||||
|
||||
|
||||
- Fixed XYPlot is not working when `a1111_prompt_style` is True
|
||||
- Fixed UI loading failure in the new version of ComfyUI
|
||||
- `easy XYInputs ModelMergeBlocks` Values can be imported from CSV files
|
||||
- Fixed `easy pipeToBasicPipe` Bug
|
||||
|
||||
|
||||
- Removed `easy imageRemBg`
|
||||
- Remove the introductory diagram and workflow files from the package to reduce the package size
|
||||
- Replaced the font file used in the generation of XY diagrams
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.1</b></summary>
|
||||
|
||||
- Fixed `easy comfyLoader` error
|
||||
- Fixed All nodes that contain the value of the image size
|
||||
- Added `easy kSamplerInpainting`
|
||||
- Added `easy pipeToBasicPipe`
|
||||
- Fixed `width` and `height` can not customize in `easy svdLoader`
|
||||
- Fixed all preview image path (Previously, it was not possible to preview the image on the Mac system)
|
||||
- Fixed `vae_name` is not working in `easy fullLoader` and `easy a1111Loader` and `easy comfyLoader`
|
||||
- Fixed `easy fullkSampler` outputs error
|
||||
- Fixed `model_override` is not working in `easy fullLoader`
|
||||
- Fixed `easy hiresFix` error
|
||||
- Fixed `easy xyplot` font file path error
|
||||
- Fixed seed that cannot be fixed when you convert `seed_num` to `easy seed`
|
||||
- Fixed `easy pipeIn` inputs bug
|
||||
- `easy preDetailerFix` have added a new parameter `optional_image`
|
||||
- Fixed `easy zero123Loader` and `easy svdLoader` model into cache.
|
||||
- Added `easy seed`
|
||||
- Fixed `image_output` default value is "Preview"
|
||||
- `easy fullLoader` and `easy a1111Loader` have added a new parameter `a1111_prompt_style`,that can reproduce the same image generated from stable-diffusion-webui on comfyui, but you need to install [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) to use this feature in the current version
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>v1.0.0</b></summary>
|
||||
|
||||
- Added `easy positive` - simple positive prompt text
|
||||
- Added `easy negative` - simple negative prompt text
|
||||
- Added `easy wildcards` - support for wildcards and hint text selected by Lora
|
||||
- Added `easy portraitMaster` - PortraitMaster v2.2
|
||||
- Added `easy loraStack` - Lora stack
|
||||
- Added `easy fullLoader` - full version of the loader
|
||||
- Added `easy zero123Loader` - simple zero123 loader
|
||||
- Added `easy svdLoader` - easy svd loader
|
||||
- Added `easy fullkSampler` - full version of the sampler (no separation)
|
||||
- Added `easy hiresFix` - support for HD repair of Pipe
|
||||
- Added `easy predetailerFix` and `easy DetailerFix` - support for Pipe detail fixing
|
||||
- Added `easy ultralyticsDetectorPipe` and `easy samLoaderPipe` - Detect loader (detail fixed input)
|
||||
- Added `easy pipein` `easy pipeout` - Pipe input and output
|
||||
- Added `easy xyPlot` - simple xyplot (more controllable parameters will be updated in the future)
|
||||
- Added `easy imageRemoveBG` - image to remove background
|
||||
- Added `easy imagePixelPerfect` - image pixel perfect
|
||||
- Added `easy poseEditor` - Pose editor
|
||||
- New UI Theme (Obsidian) - Auto-load UI by default, which can also be changed in the settings
|
||||
|
||||
- Fixed `easy globalSeed` is not working
|
||||
- Fixed an issue where all `seed_num` values were out of order due to [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) updating the chart in real time
|
||||
- Fixed `easy imageSize`, `easy imageSizeBySide`, `easy imageSizeByLongerSide` as end nodes
|
||||
- Fixed the bug that `seed_num` (random seed value) could not be read consistently in history
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Updated at 12/14/2023</b></summary>
|
||||
|
||||
- `easy a1111Loader` and `easy comfyLoader` added `batch_size` of required input parameters
|
||||
- Added the `easy controlnetLoaderADV` node
|
||||
- `easy controlnetLoaderADV` and `easy controlnetLoader` added `control_net ` of optional input parameters
|
||||
- `easy preSampling` and `easy preSamplingAdvanced` added `image_to_latent` optional input parameters
|
||||
- Added the `easy imageSizeBySide` node, which can be output as a long side or a short side
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Updated at 12/13/2023</b></summary>
|
||||
|
||||
- Added the `easy LLLiteLoader` node, if you have pre-installed the kohya-ss/ControlNet-LLLite-ComfyUI package, please move the model files in the models to `ComfyUI\models\controlnet\` (i.e. in the default controlnet path of comfy, please do not change the file name of the model, otherwise it will not be read).
|
||||
- Modify `easy controlnetLoader` to the bottom of the loader category.
|
||||
- Added size display for `easy imageSize` and `easy imageSizeByLongerSize` outputs.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Updated at 12/11/2023</b></summary>
|
||||
- Added the `showSpentTime` node to display the time spent on image diffusion and the time spent on VAE decoding images
|
||||
</details>
|
||||
|
||||
## The relevant node package involved
|
||||
|
||||
Disclaimer: Opened source was not easy. I have a lot of respect for the contributions of these original authors. I just did some integration and optimization.
|
||||
|
||||
| Nodes Name(Search Name) | Related libraries | Library-related node |
|
||||
|:-------------------------------|:----------------------------------------------------------------------------|:-------------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply... |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
|
||||
| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
|
||||
| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader |
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - Powerful and modular Stable Diffusion GUI
|
||||
|
||||
[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI Manager
|
||||
|
||||
[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - Pipe nodes (node bundles) allow users to reduce unnecessary connections
|
||||
|
||||
[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - Diffus3 gets and sets points that allow the user to detach the composition of the workflow
|
||||
|
||||
[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - General modpack 1
|
||||
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - General Modpack 2
|
||||
|
||||
[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - Make model generation independent of training resolution
|
||||
|
||||
[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - Style migration
|
||||
|
||||
[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - Face migration
|
||||
|
||||
[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - Face migration
|
||||
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss🐍
|
||||
|
||||
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - Image Preview Chooser
|
||||
|
||||
[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT custom nodes
|
||||
|
||||
## ☕️ Donation
|
||||
|
||||
**Comfyui-Easy-Use** is an GPL-licensed open source project. In order to achieve better and sustainable development of the project, i expect to gain more backers. <br>
|
||||
If my custom nodes has added value to your day, consider indulging in a coffee to fuel it further! <br>
|
||||
💖You can support me in any of the following ways:
|
||||
|
||||
- [BiliBili](https://space.bilibili.com/1840885116)
|
||||
- [Afdian](https://afdian.com/a/yolain)
|
||||
- [Wechat / Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03)
|
||||
- 🪙 Wallet Address:
|
||||
- ETH: 0x01f7CEd3245CaB3891A0ec8f528178db352EaC74
|
||||
- USDT(tron): TP3AnJXkAzfebL2GKmFAvQvXgsxzivweV6
|
||||
|
||||
(This is a newly created wallet, and if it receives sponsorship, I'll use it to rent GPUs or other GPT services for better debugging and refinement of ComfyUI-Easy-Use features.)
|
||||
|
||||
## 🌟Stargazers
|
||||
|
||||
My gratitude extends to the generous souls who bestow a star. Your support is much appreciated!
|
||||
|
||||
[](https://github.com/yolain/ComfyUI-Easy-Use/stargazers)
|
||||
+63
-8
@@ -1,5 +1,6 @@
|
||||
__version__ = "1.2.1"
|
||||
__version__ = "1.2.5"
|
||||
|
||||
import yaml
|
||||
import os
|
||||
import folder_paths
|
||||
import importlib
|
||||
@@ -24,13 +25,19 @@ for module_name in node_list:
|
||||
cwd_path = os.path.dirname(os.path.realpath(__file__))
|
||||
comfy_path = folder_paths.base_path
|
||||
|
||||
#Wildcards读取
|
||||
#Wildcards
|
||||
from .py.libs.wildcards import read_wildcard_dict
|
||||
wildcards_path = os.path.join(os.path.dirname(__file__), "wildcards")
|
||||
if os.path.exists(wildcards_path):
|
||||
read_wildcard_dict(wildcards_path)
|
||||
else:
|
||||
if not os.path.exists(wildcards_path):
|
||||
os.mkdir(wildcards_path)
|
||||
|
||||
# Add custom wildcards example
|
||||
example_path = os.path.join(wildcards_path, "example.txt")
|
||||
if not os.path.exists(example_path):
|
||||
with open(example_path, 'w') as f:
|
||||
text = "blue\nred\nyellow\ngreen\nbrown\npink\npurple\norange\nblack\nwhite"
|
||||
f.write(text)
|
||||
read_wildcard_dict(wildcards_path)
|
||||
|
||||
#Styles
|
||||
styles_path = os.path.join(os.path.dirname(__file__), "styles")
|
||||
@@ -42,6 +49,23 @@ else:
|
||||
os.mkdir(styles_path)
|
||||
os.mkdir(samples_path)
|
||||
|
||||
# Add custom styles example
|
||||
example_path = os.path.join(styles_path, "your_styles.json.example")
|
||||
if not os.path.exists(example_path):
|
||||
import json
|
||||
data = [
|
||||
{
|
||||
"name": "Example Style",
|
||||
"name_cn": "示例样式",
|
||||
"prompt": "(masterpiece), (best quality), (ultra-detailed), {prompt} ",
|
||||
"negative_prompt": "text, watermark, logo"
|
||||
},
|
||||
]
|
||||
# Write to file
|
||||
with open(example_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
# Model thumbnails
|
||||
from .py.libs.add_resources import add_static_resource
|
||||
from .py.libs.model import easyModelManager
|
||||
@@ -53,8 +77,39 @@ for model in model_config:
|
||||
continue
|
||||
add_static_resource(path, path, limit=True)
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
# get comfyui revision
|
||||
from .py.libs.utils import compare_revision
|
||||
|
||||
new_frontend_revision = 2546
|
||||
web_default_version = 'v2' if compare_revision(new_frontend_revision) else 'v1'
|
||||
# web directory
|
||||
config_path = os.path.join(cwd_path, "config.yaml")
|
||||
if os.path.isfile(config_path):
|
||||
with open(config_path, 'r') as f:
|
||||
data = yaml.load(f, Loader=yaml.FullLoader)
|
||||
if data and "WEB_VERSION" in data:
|
||||
directory = f"web_version/{data['WEB_VERSION']}"
|
||||
with open(config_path, 'w') as f:
|
||||
yaml.dump(data, f)
|
||||
elif web_default_version != 'v1':
|
||||
if not data:
|
||||
data = {'WEB_VERSION': web_default_version}
|
||||
elif 'WEB_VERSION' not in data:
|
||||
data = {**data, 'WEB_VERSION': web_default_version}
|
||||
with open(config_path, 'w') as f:
|
||||
yaml.dump(data, f)
|
||||
directory = f"web_version/{web_default_version}"
|
||||
else:
|
||||
directory = f"web_version/v1"
|
||||
if not os.path.exists(os.path.join(cwd_path, directory)):
|
||||
print(f"web root {data['WEB_VERSION']} not found, using default")
|
||||
directory = f"web_version/{web_default_version}"
|
||||
WEB_DIRECTORY = directory
|
||||
else:
|
||||
directory = f"web_version/{web_default_version}"
|
||||
WEB_DIRECTORY = directory
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"]
|
||||
|
||||
|
||||
print(f'\033[34mComfy-Easy-Use v{__version__}: \033[92mLoaded\033[0m')
|
||||
print(f'\033[34m[ComfyUI-Easy-Use] server: \033[0mv{__version__} \033[92mLoaded\033[0m')
|
||||
print(f'\033[34m[ComfyUI-Easy-Use] web root: \033[0m{os.path.join(cwd_path, directory)} \033[92mLoaded\033[0m')
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
requirements_txt="$(dirname "$0")/requirements.txt"
|
||||
requirements_repair_txt="$(dirname "$0")/repair_dependency_list.txt"
|
||||
python_exec="../../../python_embeded/python.exe"
|
||||
aki_python_exec="../../python/python.exe"
|
||||
|
||||
echo "Installing EasyUse Requirements..."
|
||||
|
||||
if [ -f "$python_exec" ]; then
|
||||
echo "Installing with ComfyUI Portable"
|
||||
"$python_exec" -s -m pip install -r "$requirements_txt"
|
||||
elif [ -f "$aki_python_exec" ]; then
|
||||
echo "Installing with ComfyUI Aki"
|
||||
"$aki_python_exec" -s -m pip install -r "$requirements_txt"
|
||||
while IFS= read -r line; do
|
||||
"$aki_python_exec" -s -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple "$line"
|
||||
done < "$requirements_repair_txt"
|
||||
else
|
||||
echo "Installing with system Python"
|
||||
pip install -r "$requirements_txt"
|
||||
fi
|
||||
|
||||
read -p "Press any key to continue..."
|
||||
@@ -7,7 +7,6 @@ import folder_paths
|
||||
from folder_paths import get_directory_by_type
|
||||
from server import PromptServer
|
||||
from .config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_STYLES_SAMPLES
|
||||
from .logic import ConvertAnything
|
||||
from .libs.model import easyModelManager
|
||||
from .libs.utils import getMetadata, cleanGPUUsedForce, get_local_filepath
|
||||
from .libs.cache import remove_cache
|
||||
@@ -31,6 +30,17 @@ def cleanGPU(request):
|
||||
return web.Response(status=500)
|
||||
pass
|
||||
|
||||
@PromptServer.instance.routes.post("/easyuse/removecache")
|
||||
async def removecache(request):
|
||||
post = await request.post()
|
||||
key = post.get("key")
|
||||
try:
|
||||
remove_cache(key)
|
||||
return web.Response(status=200)
|
||||
except Exception as e:
|
||||
return web.Response(status=500)
|
||||
pass
|
||||
|
||||
@PromptServer.instance.routes.post("/easyuse/translate")
|
||||
async def translate(request):
|
||||
post = await request.post()
|
||||
@@ -123,18 +133,6 @@ async def getStylesImage(request):
|
||||
return web.Response(text=FOOOCUS_STYLES_SAMPLES + name + '.jpg')
|
||||
return web.Response(status=400)
|
||||
|
||||
# convert type
|
||||
@PromptServer.instance.routes.post("/easyuse/convert")
|
||||
async def convertType(request):
|
||||
post = await request.post()
|
||||
type = post.get('type')
|
||||
if type:
|
||||
ConvertAnything.RETURN_TYPES = (type.upper(),)
|
||||
ConvertAnything.RETURN_NAMES = (type,)
|
||||
return web.Response(status=200)
|
||||
else:
|
||||
return web.Response(status=400)
|
||||
|
||||
# get models lists
|
||||
@PromptServer.instance.routes.get("/easyuse/models/list")
|
||||
async def getModelsList(request):
|
||||
@@ -150,11 +148,15 @@ async def getModelsList(request):
|
||||
# get models thumbnails
|
||||
@PromptServer.instance.routes.get("/easyuse/models/thumbnail")
|
||||
async def getModelsThumbnail(request):
|
||||
limit = 500
|
||||
if "limit" in request.rel_url.query:
|
||||
limit = request.rel_url.query.get("limit")
|
||||
limit = int(limit)
|
||||
checkpoints = folder_paths.get_filename_list("checkpoints_thumb")
|
||||
loras = folder_paths.get_filename_list("loras_thumb")
|
||||
checkpoints_full = []
|
||||
loras_full = []
|
||||
if len(checkpoints) + len(loras) >= 500:
|
||||
if len(checkpoints) + len(loras) >= limit:
|
||||
return web.Response(status=400)
|
||||
for index, i in enumerate(checkpoints):
|
||||
full_path = folder_paths.get_full_path('checkpoints_thumb', str(i))
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#credit to comfyanonymous for this module
|
||||
#from https://github.com/comfyanonymous/ComfyUI_bitsandbytes_NF4
|
||||
import comfy.ops
|
||||
import torch
|
||||
import folder_paths
|
||||
from ..libs.utils import install_package
|
||||
|
||||
try:
|
||||
from bitsandbytes.nn.modules import Params4bit, QuantState
|
||||
except ImportError:
|
||||
Params4bit = torch.nn.Parameter
|
||||
raise ImportError("Please install bitsandbytes>=0.43.3")
|
||||
|
||||
def functional_linear_4bits(x, weight, bias):
|
||||
try:
|
||||
install_package("bitsandbytes", "0.43.3", True, "0.43.3")
|
||||
import bitsandbytes as bnb
|
||||
except ImportError:
|
||||
raise ImportError("Please install bitsandbytes>=0.43.3")
|
||||
|
||||
out = bnb.matmul_4bit(x, weight.t(), bias=bias, quant_state=weight.quant_state)
|
||||
out = out.to(x)
|
||||
return out
|
||||
|
||||
|
||||
def copy_quant_state(state, device: torch.device = None):
|
||||
if state is None:
|
||||
return None
|
||||
|
||||
device = device or state.absmax.device
|
||||
|
||||
state2 = (
|
||||
QuantState(
|
||||
absmax=state.state2.absmax.to(device),
|
||||
shape=state.state2.shape,
|
||||
code=state.state2.code.to(device),
|
||||
blocksize=state.state2.blocksize,
|
||||
quant_type=state.state2.quant_type,
|
||||
dtype=state.state2.dtype,
|
||||
)
|
||||
if state.nested
|
||||
else None
|
||||
)
|
||||
|
||||
return QuantState(
|
||||
absmax=state.absmax.to(device),
|
||||
shape=state.shape,
|
||||
code=state.code.to(device),
|
||||
blocksize=state.blocksize,
|
||||
quant_type=state.quant_type,
|
||||
dtype=state.dtype,
|
||||
offset=state.offset.to(device) if state.nested else None,
|
||||
state2=state2,
|
||||
)
|
||||
|
||||
|
||||
class ForgeParams4bit(Params4bit):
|
||||
|
||||
def to(self, *args, **kwargs):
|
||||
device, dtype, non_blocking, convert_to_format = torch._C._nn._parse_to(*args, **kwargs)
|
||||
if device is not None and device.type == "cuda" and not self.bnb_quantized:
|
||||
return self._quantize(device)
|
||||
else:
|
||||
n = ForgeParams4bit(
|
||||
torch.nn.Parameter.to(self, device=device, dtype=dtype, non_blocking=non_blocking),
|
||||
requires_grad=self.requires_grad,
|
||||
quant_state=copy_quant_state(self.quant_state, device),
|
||||
blocksize=self.blocksize,
|
||||
compress_statistics=self.compress_statistics,
|
||||
quant_type=self.quant_type,
|
||||
quant_storage=self.quant_storage,
|
||||
bnb_quantized=self.bnb_quantized,
|
||||
module=self.module
|
||||
)
|
||||
self.module.quant_state = n.quant_state
|
||||
self.data = n.data
|
||||
self.quant_state = n.quant_state
|
||||
return n
|
||||
|
||||
class ForgeLoader4Bit(torch.nn.Module):
|
||||
def __init__(self, *, device, dtype, quant_type, **kwargs):
|
||||
super().__init__()
|
||||
self.dummy = torch.nn.Parameter(torch.empty(1, device=device, dtype=dtype))
|
||||
self.weight = None
|
||||
self.quant_state = None
|
||||
self.bias = None
|
||||
self.quant_type = quant_type
|
||||
|
||||
def _save_to_state_dict(self, destination, prefix, keep_vars):
|
||||
super()._save_to_state_dict(destination, prefix, keep_vars)
|
||||
quant_state = getattr(self.weight, "quant_state", None)
|
||||
if quant_state is not None:
|
||||
for k, v in quant_state.as_dict(packed=True).items():
|
||||
destination[prefix + "weight." + k] = v if keep_vars else v.detach()
|
||||
return
|
||||
|
||||
def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs):
|
||||
quant_state_keys = {k[len(prefix + "weight."):] for k in state_dict.keys() if k.startswith(prefix + "weight.")}
|
||||
|
||||
if any('bitsandbytes' in k for k in quant_state_keys):
|
||||
quant_state_dict = {k: state_dict[prefix + "weight." + k] for k in quant_state_keys}
|
||||
|
||||
self.weight = ForgeParams4bit().from_prequantized(
|
||||
data=state_dict[prefix + 'weight'],
|
||||
quantized_stats=quant_state_dict,
|
||||
requires_grad=False,
|
||||
device=self.dummy.device,
|
||||
module=self
|
||||
)
|
||||
self.quant_state = self.weight.quant_state
|
||||
|
||||
if prefix + 'bias' in state_dict:
|
||||
self.bias = torch.nn.Parameter(state_dict[prefix + 'bias'].to(self.dummy))
|
||||
|
||||
del self.dummy
|
||||
elif hasattr(self, 'dummy'):
|
||||
if prefix + 'weight' in state_dict:
|
||||
self.weight = ForgeParams4bit(
|
||||
state_dict[prefix + 'weight'].to(self.dummy),
|
||||
requires_grad=False,
|
||||
compress_statistics=True,
|
||||
quant_type=self.quant_type,
|
||||
quant_storage=torch.uint8,
|
||||
module=self,
|
||||
)
|
||||
self.quant_state = self.weight.quant_state
|
||||
|
||||
if prefix + 'bias' in state_dict:
|
||||
self.bias = torch.nn.Parameter(state_dict[prefix + 'bias'].to(self.dummy))
|
||||
|
||||
del self.dummy
|
||||
else:
|
||||
super()._load_from_state_dict(state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs)
|
||||
|
||||
current_device = None
|
||||
current_dtype = None
|
||||
current_manual_cast_enabled = False
|
||||
current_bnb_dtype = None
|
||||
|
||||
class OPS(comfy.ops.manual_cast):
|
||||
class Linear(ForgeLoader4Bit):
|
||||
def __init__(self, *args, device=None, dtype=None, **kwargs):
|
||||
super().__init__(device=device, dtype=dtype, quant_type=current_bnb_dtype)
|
||||
self.parameters_manual_cast = current_manual_cast_enabled
|
||||
|
||||
def forward(self, x):
|
||||
self.weight.quant_state = self.quant_state
|
||||
|
||||
if self.bias is not None and self.bias.dtype != x.dtype:
|
||||
# Maybe this can also be set to all non-bnb ops since the cost is very low.
|
||||
# And it only invokes one time, and most linear does not have bias
|
||||
self.bias.data = self.bias.data.to(x.dtype)
|
||||
|
||||
if not self.parameters_manual_cast:
|
||||
return functional_linear_4bits(x, self.weight, self.bias)
|
||||
elif not self.weight.bnb_quantized:
|
||||
assert x.device.type == 'cuda', 'BNB Must Use CUDA as Computation Device!'
|
||||
layer_original_device = self.weight.device
|
||||
self.weight = self.weight._quantize(x.device)
|
||||
bias = self.bias.to(x.device) if self.bias is not None else None
|
||||
out = functional_linear_4bits(x, self.weight, bias)
|
||||
self.weight = self.weight.to(layer_original_device)
|
||||
return out
|
||||
else:
|
||||
weight, bias, signal = weights_manual_cast(self, x, skip_weight_dtype=True, skip_bias_dtype=True)
|
||||
with main_stream_worker(weight, bias, signal):
|
||||
return functional_linear_4bits(x, weight, bias)
|
||||
@@ -664,7 +664,7 @@ def add_brushnet_patch(model, brushnet, torch_dtype, conditioning_latents,
|
||||
is_SDXL = isinstance(model.model.model_config, comfy.supported_models.SDXL)
|
||||
|
||||
if is_SDXL:
|
||||
input_blocks = [[0, comfy.ops.disable_weight_init.Conv2d],
|
||||
input_blocks = [[0, comfy.ops.manual_cast.Conv2d],
|
||||
[1, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock],
|
||||
[2, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock],
|
||||
[3, comfy.ldm.modules.diffusionmodules.openaimodel.Downsample],
|
||||
@@ -686,7 +686,7 @@ def add_brushnet_patch(model, brushnet, torch_dtype, conditioning_latents,
|
||||
[7, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock],
|
||||
[8, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock]]
|
||||
else:
|
||||
input_blocks = [[0, comfy.ops.disable_weight_init.Conv2d],
|
||||
input_blocks = [[0, comfy.ops.manual_cast.Conv2d],
|
||||
[1, comfy.ldm.modules.attention.SpatialTransformer],
|
||||
[2, comfy.ldm.modules.attention.SpatialTransformer],
|
||||
[3, comfy.ldm.modules.diffusionmodules.openaimodel.Downsample],
|
||||
|
||||
@@ -380,7 +380,7 @@ class EmbeddingLayerWithFixes(nn.Module):
|
||||
|
||||
return torch.cat(new_embedding, dim=0)
|
||||
|
||||
def forward(self, input_ids: torch.Tensor, external_embeddings: Optional[List[dict]] = None):
|
||||
def forward(self, input_ids: torch.Tensor, external_embeddings: Optional[List[dict]] = None, out_dtype = None):
|
||||
"""The forward function.
|
||||
|
||||
Args:
|
||||
@@ -397,7 +397,7 @@ class EmbeddingLayerWithFixes(nn.Module):
|
||||
input_ids = input_ids.unsqueeze(0)
|
||||
|
||||
if external_embeddings is None and not self.external_embeddings:
|
||||
return self.wrapped(input_ids)
|
||||
return self.wrapped(input_ids, out_dtype=out_dtype)
|
||||
|
||||
input_ids_fwd = self.replace_input_ids(input_ids)
|
||||
inputs_embeds = self.wrapped(input_ids_fwd)
|
||||
@@ -416,7 +416,7 @@ class EmbeddingLayerWithFixes(nn.Module):
|
||||
new_embedding = self.replace_embeddings(input_id, new_embedding, external_embedding)
|
||||
vecs.append(new_embedding)
|
||||
|
||||
return torch.stack(vecs)
|
||||
return torch.stack(vecs).to(out_dtype)
|
||||
|
||||
|
||||
def add_tokens(
|
||||
|
||||
+36
-17
@@ -190,6 +190,9 @@ REMBG_DIR = os.path.join(folder_paths.models_dir, "rembg")
|
||||
REMBG_MODELS = {
|
||||
"RMBG-1.4": {
|
||||
"model_url": "https://huggingface.co/briaai/RMBG-1.4/resolve/main/model.pth"
|
||||
},
|
||||
"RMBG-2.0": {
|
||||
"model_url": "briaai/RMBG-2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +200,7 @@ REMBG_MODELS = {
|
||||
IPADAPTER_DIR = os.path.join(folder_paths.models_dir, "ipadapter")
|
||||
IPADAPTER_MODELS = {
|
||||
"LIGHT - SD1.5 only (low strength)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_light_v11.bin"
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -205,7 +208,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"STANDARD (medium strength)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15.safetensors"
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -213,7 +216,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"VIT-G (medium strength)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_vit-G.safetensors"
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -221,23 +224,33 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"PLUS (high strength)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus_sd15.safetensors"
|
||||
},
|
||||
"sdxl": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus_sdxl_vit-h.safetensors"
|
||||
}
|
||||
},
|
||||
"PLUS (kolors genernal)":{
|
||||
"sd15":{
|
||||
"model_url":""
|
||||
"PLUS (kolors genernal)": {
|
||||
"sd1": {
|
||||
"model_url": ""
|
||||
},
|
||||
"sdxl":{
|
||||
"sdxl": {
|
||||
"model_url":"https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/ip_adapter_plus_general.bin"
|
||||
}
|
||||
},
|
||||
"REGULAR - FLUX and SD3.5 only (high strength)": {
|
||||
"flux": {
|
||||
"model_url": "https://huggingface.co/InstantX/FLUX.1-dev-IP-Adapter/resolve/main/ip-adapter.bin",
|
||||
"model_file_name": "ip-adapter_flux_1_dev.bin",
|
||||
},
|
||||
"sd3": {
|
||||
"model_url": "https://huggingface.co/InstantX/SD3.5-Large-IP-Adapter/resolve/main/ip-adapter.bin",
|
||||
"model_file_name": "ip-adapter_sd35.bin",
|
||||
},
|
||||
},
|
||||
"PLUS FACE (portraits)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus-face_sd15.safetensors"
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -245,7 +258,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"FULL FACE - SD1.5 only (portraits stronger)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-full-face_sd15.safetensors"
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -253,7 +266,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"FACEID": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15.bin",
|
||||
"lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15_lora.safetensors"
|
||||
},
|
||||
@@ -263,7 +276,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"FACEID PLUS - SD1.5 only": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15.bin",
|
||||
"lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15_lora.safetensors"
|
||||
},
|
||||
@@ -273,7 +286,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"FACEID PLUS V2": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15.bin",
|
||||
"lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15_lora.safetensors"
|
||||
},
|
||||
@@ -283,7 +296,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"FACEID PLUS KOLORS":{
|
||||
"sd15":{
|
||||
"sd1":{
|
||||
|
||||
},
|
||||
"sdxl":{
|
||||
@@ -291,7 +304,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"FACEID PORTRAIT (style transfer)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait-v11_sd15.bin",
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -299,7 +312,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"FACEID PORTRAIT UNNORM - SDXL only (strong)": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url":""
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -307,7 +320,7 @@ IPADAPTER_MODELS = {
|
||||
}
|
||||
},
|
||||
"COMPOSITION": {
|
||||
"sd15": {
|
||||
"sd1": {
|
||||
"model_url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sd15.safetensors"
|
||||
},
|
||||
"sdxl": {
|
||||
@@ -321,6 +334,9 @@ IPADAPTER_CLIPVISION_MODELS = {
|
||||
},
|
||||
"clip-vit-h-14-laion2B-s32B-b79K":{
|
||||
"model_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_pytorch_model.safetensors"
|
||||
},
|
||||
"sigclip_vision_patch14_384":{
|
||||
"model_url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +365,9 @@ HUMANPARSING_MODELS = {
|
||||
"parsing_lip": {
|
||||
"model_url": "https://huggingface.co/levihsu/OOTDiffusion/resolve/main/checkpoints/humanparsing/parsing_lip.onnx",
|
||||
},
|
||||
"human-parts":{
|
||||
"model_url":"https://huggingface.co/Metal3d/deeplabv3p-resnet50-human/resolve/main/deeplabv3p-resnet50-human.onnx",
|
||||
}
|
||||
}
|
||||
|
||||
#mediapipe
|
||||
|
||||
@@ -118,7 +118,6 @@ def load_pixart(model_path, model_conf=None):
|
||||
model,
|
||||
load_device=load_device,
|
||||
offload_device=offload_device,
|
||||
current_device="cpu",
|
||||
)
|
||||
return model_patcher
|
||||
|
||||
|
||||
+8199
-7750
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,139 @@
|
||||
#credit to Acly for this module
|
||||
#from https://github.com/Acly/comfyui-inpaint-nodes
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import comfy
|
||||
from comfy.model_base import BaseModel
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
from comfy.model_management import cast_to_device
|
||||
|
||||
from ..libs.log import log_node_warn, log_node_error, log_node_info
|
||||
|
||||
class InpaintHead(torch.nn.Module):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.head = torch.nn.Parameter(torch.empty(size=(320, 5, 3, 3), device="cpu"))
|
||||
|
||||
def __call__(self, x):
|
||||
x = F.pad(x, (1, 1, 1, 1), "replicate")
|
||||
return F.conv2d(x, weight=self.head)
|
||||
|
||||
# injected_model_patcher_calculate_weight = False
|
||||
# original_calculate_weight = None
|
||||
|
||||
class applyFooocusInpaint:
|
||||
def calculate_weight_patched(self, patches, weight, key, intermediate_dtype=torch.float32):
|
||||
remaining = []
|
||||
|
||||
for p in patches:
|
||||
alpha = p[0]
|
||||
v = p[1]
|
||||
|
||||
is_fooocus_patch = isinstance(v, tuple) and len(v) == 2 and v[0] == "fooocus"
|
||||
if not is_fooocus_patch:
|
||||
remaining.append(p)
|
||||
continue
|
||||
|
||||
if alpha != 0.0:
|
||||
v = v[1]
|
||||
w1 = cast_to_device(v[0], weight.device, torch.float32)
|
||||
if w1.shape == weight.shape:
|
||||
w_min = cast_to_device(v[1], weight.device, torch.float32)
|
||||
w_max = cast_to_device(v[2], weight.device, torch.float32)
|
||||
w1 = (w1 / 255.0) * (w_max - w_min) + w_min
|
||||
weight += alpha * cast_to_device(w1, weight.device, weight.dtype)
|
||||
else:
|
||||
print(
|
||||
f"[ApplyFooocusInpaint] Shape mismatch {key}, weight not merged ({w1.shape} != {weight.shape})"
|
||||
)
|
||||
|
||||
if len(remaining) > 0:
|
||||
return self.original_calculate_weight(remaining, weight, key, intermediate_dtype)
|
||||
return weight
|
||||
|
||||
def __enter__(self):
|
||||
try:
|
||||
print("[comfyui-easy-use] Injecting patched comfy.lora.calculate_weight.calculate_weight")
|
||||
self.original_calculate_weight = comfy.lora.calculate_weight
|
||||
comfy.lora.calculate_weight = self.calculate_weight_patched
|
||||
except AttributeError:
|
||||
print("[comfyui-easy-use] Injecting patched comfy.model_patcher.ModelPatcher.calculate_weight")
|
||||
self.original_calculate_weight = ModelPatcher.calculate_weight
|
||||
ModelPatcher.calculate_weight = self.calculate_weight_patched
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
try:
|
||||
comfy.lora.calculate_weight = self.original_calculate_weight
|
||||
except:
|
||||
ModelPatcher.calculate_weight = self.original_calculate_weight
|
||||
|
||||
# def inject_patched_calculate_weight():
|
||||
# global injected_model_patcher_calculate_weight
|
||||
# if not injected_model_patcher_calculate_weight:
|
||||
# try:
|
||||
# print("[comfyui-easy-use] Injecting patched comfy.lora.calculate_weight.calculate_weight")
|
||||
# original_calculate_weight = comfy.lora.calculate_weight
|
||||
# comfy.lora.original_calculate_weight = original_calculate_weight
|
||||
# comfy.lora.calculate_weight = calculate_weight_patched
|
||||
# except AttributeError:
|
||||
# print("[comfyui-easy-use] Injecting patched comfy.model_patcher.ModelPatcher.calculate_weight")
|
||||
# original_calculate_weight = ModelPatcher.calculate_weight
|
||||
# ModelPatcher.original_calculate_weight = original_calculate_weight
|
||||
# ModelPatcher.calculate_weight = calculate_weight_patched
|
||||
# injected_model_patcher_calculate_weight = True
|
||||
|
||||
|
||||
class InpaintWorker:
|
||||
def __init__(self, node_name):
|
||||
self.node_name = node_name if node_name is not None else ""
|
||||
|
||||
def load_fooocus_patch(self, lora: dict, to_load: dict):
|
||||
patch_dict = {}
|
||||
loaded_keys = set()
|
||||
for key in to_load.values():
|
||||
if value := lora.get(key, None):
|
||||
patch_dict[key] = ("fooocus", value)
|
||||
loaded_keys.add(key)
|
||||
|
||||
not_loaded = sum(1 for x in lora if x not in loaded_keys)
|
||||
if not_loaded > 0:
|
||||
log_node_info(self.node_name,
|
||||
f"{len(loaded_keys)} Lora keys loaded, {not_loaded} remaining keys not found in model."
|
||||
)
|
||||
return patch_dict
|
||||
|
||||
def _input_block_patch(self, h: torch.Tensor, transformer_options: dict):
|
||||
if transformer_options["block"][1] == 0:
|
||||
if self._inpaint_block is None or self._inpaint_block.shape != h.shape:
|
||||
assert self._inpaint_head_feature is not None
|
||||
batch = h.shape[0] // self._inpaint_head_feature.shape[0]
|
||||
self._inpaint_block = self._inpaint_head_feature.to(h).repeat(batch, 1, 1, 1)
|
||||
h = h + self._inpaint_block
|
||||
return h
|
||||
|
||||
def patch(self, model, latent, patch):
|
||||
base_model: BaseModel = model.model
|
||||
latent_pixels = base_model.process_latent_in(latent["samples"])
|
||||
noise_mask = latent["noise_mask"].round()
|
||||
latent_mask = F.max_pool2d(noise_mask, (8, 8)).round().to(latent_pixels)
|
||||
|
||||
inpaint_head_model, inpaint_lora = patch
|
||||
feed = torch.cat([latent_mask, latent_pixels], dim=1)
|
||||
inpaint_head_model.to(device=feed.device, dtype=feed.dtype)
|
||||
self._inpaint_head_feature = inpaint_head_model(feed)
|
||||
self._inpaint_block = None
|
||||
|
||||
lora_keys = comfy.lora.model_lora_keys_unet(model.model, {})
|
||||
lora_keys.update({x: x for x in base_model.state_dict().keys()})
|
||||
loaded_lora = self.load_fooocus_patch(inpaint_lora, lora_keys)
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_input_block_patch(self._input_block_patch)
|
||||
patched = m.add_patches(loaded_lora, 1.0)
|
||||
m.model_options['transformer_options']['fooocus'] = True
|
||||
not_patched_count = sum(1 for x in loaded_lora if x not in patched)
|
||||
if not_patched_count > 0:
|
||||
log_node_error(self.node_name, f"Failed to patch {not_patched_count} keys")
|
||||
|
||||
# inject_patched_calculate_weight()
|
||||
return (m,)
|
||||
@@ -1,3 +1,6 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
from .parsing_api import onnx_inference
|
||||
from ..libs.utils import install_package
|
||||
|
||||
@@ -21,3 +24,86 @@ class HumanParsing:
|
||||
parsed_image, mask = onnx_inference(self.session, input_image, mask_components)
|
||||
return parsed_image, mask
|
||||
|
||||
|
||||
class HumanParts:
|
||||
|
||||
def __init__(self, model_path):
|
||||
self.model_path = model_path
|
||||
self.session = None
|
||||
# self.classes_dict = {
|
||||
# "background": 0,
|
||||
# "hair": 2,
|
||||
# "glasses": 4,
|
||||
# "top-clothes": 5,
|
||||
# "bottom-clothes": 9,
|
||||
# "torso-skin": 10,
|
||||
# "face": 13,
|
||||
# "left-arm": 14,
|
||||
# "right-arm": 15,
|
||||
# "left-leg": 16,
|
||||
# "right-leg": 17,
|
||||
# "left-foot": 18,
|
||||
# "right-foot": 19,
|
||||
# },
|
||||
self.classes = [0, 13, 2, 4, 5, 9, 10, 14, 15, 16, 17, 18, 19]
|
||||
|
||||
|
||||
def __call__(self, input_image, mask_components):
|
||||
if self.session is None:
|
||||
install_package('onnxruntime')
|
||||
import onnxruntime as ort
|
||||
|
||||
self.session = ort.InferenceSession(self.model_path, providers=['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider'])
|
||||
|
||||
mask, = self.get_mask(self.session, input_image, 0, mask_components)
|
||||
return mask
|
||||
|
||||
def get_mask(self, model, image, rotation, mask_components):
|
||||
image = image.squeeze(0)
|
||||
image_np = image.numpy() * 255
|
||||
|
||||
pil_image = Image.fromarray(image_np.astype(np.uint8))
|
||||
original_size = pil_image.size # to resize the mask later
|
||||
# resize to 512x512 as the model expects
|
||||
pil_image = pil_image.resize((512, 512))
|
||||
center = (256, 256)
|
||||
|
||||
if rotation != 0:
|
||||
pil_image = pil_image.rotate(rotation, center=center)
|
||||
|
||||
# normalize the image
|
||||
image_np = np.array(pil_image).astype(np.float32) / 127.5 - 1
|
||||
image_np = np.expand_dims(image_np, axis=0)
|
||||
|
||||
# use the onnx model to get the mask
|
||||
input_name = model.get_inputs()[0].name
|
||||
output_name = model.get_outputs()[0].name
|
||||
result = model.run([output_name], {input_name: image_np})
|
||||
result = np.array(result[0]).argmax(axis=3).squeeze(0)
|
||||
|
||||
score: int = 0
|
||||
|
||||
mask = np.zeros_like(result)
|
||||
for class_index in mask_components:
|
||||
detected = result == self.classes[class_index]
|
||||
mask[detected] = 255
|
||||
score += mask.sum()
|
||||
|
||||
# back to the original size
|
||||
mask_image = Image.fromarray(mask.astype(np.uint8), mode="L")
|
||||
if rotation != 0:
|
||||
mask_image = mask_image.rotate(-rotation, center=center)
|
||||
|
||||
mask_image = mask_image.resize(original_size)
|
||||
|
||||
# and back to numpy...
|
||||
mask = np.array(mask_image).astype(np.float32) / 255
|
||||
|
||||
# add 2 dimensions to match the expected output
|
||||
mask = np.expand_dims(mask, axis=0)
|
||||
mask = np.expand_dims(mask, axis=0)
|
||||
# ensure to return a "binary mask_image"
|
||||
|
||||
del image_np, result # free up memory, maybe not necessary
|
||||
|
||||
return (torch.from_numpy(mask.astype(np.uint8)),)
|
||||
+75
-78
@@ -8,10 +8,9 @@ import comfy.model_management
|
||||
from comfy.sd import load_unet
|
||||
from comfy.ldm.models.autoencoder import AutoencoderKL
|
||||
from comfy.model_base import BaseModel
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
from PIL import Image
|
||||
from nodes import VAEEncode
|
||||
|
||||
from ..layer_diffuse.model import ModelPatcher, calculate_weight_adjust_channel
|
||||
from ..libs.image import np2tensor, pil2tensor
|
||||
|
||||
class UnetParams(TypedDict):
|
||||
@@ -20,7 +19,6 @@ class UnetParams(TypedDict):
|
||||
c: dict
|
||||
cond_or_uncond: torch.Tensor
|
||||
|
||||
|
||||
class VAEEncodeArgMax(VAEEncode):
|
||||
def encode(self, vae, pixels):
|
||||
assert isinstance(
|
||||
@@ -68,85 +66,78 @@ class ICLight:
|
||||
|
||||
def generate_lighting_image(self, original_image, direction):
|
||||
_, image_height, image_width, _ = original_image.shape
|
||||
match direction:
|
||||
case 'Left Light':
|
||||
gradient = np.linspace(255, 0, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case 'Right Light':
|
||||
gradient = np.linspace(0, 255, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case 'Top Light':
|
||||
gradient = np.linspace(255, 0, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case 'Bottom Light':
|
||||
gradient = np.linspace(0, 255, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case 'Circle Light':
|
||||
x = np.linspace(-1, 1, image_width)
|
||||
y = np.linspace(-1, 1, image_height)
|
||||
x, y = np.meshgrid(x, y)
|
||||
r = np.sqrt(x ** 2 + y ** 2)
|
||||
r = r / r.max()
|
||||
color1 = np.array([0, 0, 0])[np.newaxis, np.newaxis, :]
|
||||
color2 = np.array([255, 255, 255])[np.newaxis, np.newaxis, :]
|
||||
gradient = (color1 * r[..., np.newaxis] + color2 * (1 - r)[..., np.newaxis]).astype(np.uint8)
|
||||
image = pil2tensor(Image.fromarray(gradient))
|
||||
return image
|
||||
case _:
|
||||
image = pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
|
||||
return image
|
||||
if direction == 'Left Light':
|
||||
gradient = np.linspace(255, 0, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
elif direction == 'Right Light':
|
||||
gradient = np.linspace(0, 255, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
elif direction == 'Top Light':
|
||||
gradient = np.linspace(255, 0, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
elif direction == 'Bottom Light':
|
||||
gradient = np.linspace(0, 255, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
elif direction == 'Circle Light':
|
||||
x = np.linspace(-1, 1, image_width)
|
||||
y = np.linspace(-1, 1, image_height)
|
||||
x, y = np.meshgrid(x, y)
|
||||
r = np.sqrt(x ** 2 + y ** 2)
|
||||
r = r / r.max()
|
||||
color1 = np.array([0, 0, 0])[np.newaxis, np.newaxis, :]
|
||||
color2 = np.array([255, 255, 255])[np.newaxis, np.newaxis, :]
|
||||
gradient = (color1 * r[..., np.newaxis] + color2 * (1 - r)[..., np.newaxis]).astype(np.uint8)
|
||||
image = pil2tensor(Image.fromarray(gradient))
|
||||
return image
|
||||
else:
|
||||
image = pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
|
||||
return image
|
||||
|
||||
def generate_source_image(self, original_image, source):
|
||||
batch_size, image_height, image_width, _ = original_image.shape
|
||||
match source:
|
||||
case 'Use Flipped Background Image':
|
||||
if batch_size < 2:
|
||||
raise ValueError('Must be at least 2 image to use flipped background image.')
|
||||
original_image = [img.unsqueeze(0) for img in original_image]
|
||||
image = torch.flip(original_image[1], [2])
|
||||
return image
|
||||
case 'Ambient':
|
||||
input_bg = np.zeros(shape=(image_height, image_width, 3), dtype=np.uint8) + 64
|
||||
return np2tensor(input_bg)
|
||||
case 'Left Light':
|
||||
gradient = np.linspace(224, 32, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case 'Right Light':
|
||||
gradient = np.linspace(32, 224, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case 'Top Light':
|
||||
gradient = np.linspace(224, 32, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case 'Bottom Light':
|
||||
gradient = np.linspace(32, 224, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
case _:
|
||||
image = pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
|
||||
return image
|
||||
if source == 'Use Flipped Background Image':
|
||||
if batch_size < 2:
|
||||
raise ValueError('Must be at least 2 image to use flipped background image.')
|
||||
original_image = [img.unsqueeze(0) for img in original_image]
|
||||
image = torch.flip(original_image[1], [2])
|
||||
return image
|
||||
elif source == 'Ambient':
|
||||
input_bg = np.zeros(shape=(image_height, image_width, 3), dtype=np.uint8) + 64
|
||||
return np2tensor(input_bg)
|
||||
elif source == 'Left Light':
|
||||
gradient = np.linspace(224, 32, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
elif source == 'Right Light':
|
||||
gradient = np.linspace(32, 224, image_width)
|
||||
image = np.tile(gradient, (image_height, 1))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
elif source == 'Top Light':
|
||||
gradient = np.linspace(224, 32, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
elif source == 'Bottom Light':
|
||||
gradient = np.linspace(32, 224, image_height)[:, None]
|
||||
image = np.tile(gradient, (1, image_width))
|
||||
input_bg = np.stack((image,) * 3, axis=-1).astype(np.uint8)
|
||||
return np2tensor(input_bg)
|
||||
else:
|
||||
image = pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0)))
|
||||
return image
|
||||
|
||||
|
||||
def apply(self, ic_model_path, model: ModelPatcher, c_concat: dict, ic_model=None) -> Tuple[ModelPatcher]:
|
||||
try:
|
||||
ModelPatcher.calculate_weight = calculate_weight_adjust_channel(ModelPatcher.calculate_weight)
|
||||
except:
|
||||
pass
|
||||
|
||||
def apply(self, ic_model_path, model, c_concat: dict, ic_model=None) -> Tuple[ModelPatcher]:
|
||||
device = comfy.model_management.get_torch_device()
|
||||
dtype = comfy.model_management.unet_dtype()
|
||||
work_model = model.clone()
|
||||
@@ -179,7 +170,13 @@ class ICLight:
|
||||
|
||||
work_model.add_patches(
|
||||
patches={
|
||||
("diffusion_model." + key): (value.to(dtype=dtype, device=device),)
|
||||
("diffusion_model." + key): (
|
||||
'diff',
|
||||
[
|
||||
value.to(dtype=dtype, device=device),
|
||||
{"pad_weight": key == 'input_blocks.0.0.weight'}
|
||||
]
|
||||
)
|
||||
for key, value in ic_model_state_dict.items()
|
||||
}
|
||||
)
|
||||
|
||||
+544
-92
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
import folder_paths
|
||||
import torch
|
||||
@@ -7,18 +8,21 @@ import comfy.utils
|
||||
import comfy.model_management
|
||||
from comfy_extras.nodes_compositing import JoinImageWithAlpha
|
||||
from server import PromptServer
|
||||
from nodes import MAX_RESOLUTION
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
from nodes import MAX_RESOLUTION, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageOps
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
import torch.nn.functional as F
|
||||
from torchvision.transforms import Resize, CenterCrop, GaussianBlur
|
||||
from torchvision.transforms.functional import to_pil_image
|
||||
from .libs.log import log_node_info
|
||||
from .libs.utils import AlwaysEqualProxy
|
||||
from .libs.utils import AlwaysEqualProxy, ByPassTypeTuple
|
||||
from .libs.cache import cache, update_cache, remove_cache
|
||||
from .libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask
|
||||
from .libs.colorfix import adain_color_fix, wavelet_color_fix
|
||||
from .libs.chooser import ChooserMessage, ChooserCancelled
|
||||
from .config import REMBG_DIR, REMBG_MODELS, HUMANPARSING_MODELS, MEDIAPIPE_MODELS, MEDIAPIPE_DIR
|
||||
|
||||
|
||||
any_type = AlwaysEqualProxy("*")
|
||||
# 图像数量
|
||||
class imageCount:
|
||||
@classmethod
|
||||
@@ -318,6 +322,30 @@ class imageScaleDownToSize(imageScaleDownBy):
|
||||
scale_by = min(scale_by, 1.0)
|
||||
return self.image_scale_down_by(images, scale_by)
|
||||
|
||||
class imageScaleToNormPixels:
|
||||
upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"]
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"upscale_method": (s.upscale_methods,),
|
||||
"scale_by": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 8.0, "step": 0.01}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "scale"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
def scale(self, image, upscale_method, scale_by):
|
||||
height, width = image.shape[1:3]
|
||||
width = int(width * scale_by - width * scale_by % 8)
|
||||
height = int(height * scale_by - height * scale_by % 8)
|
||||
upscale_image_cls = ALL_NODE_CLASS_MAPPINGS['ImageScale']
|
||||
image, = upscale_image_cls().upscale(image, upscale_method, width, height, "disabled")
|
||||
return (image,)
|
||||
|
||||
# 图像比率
|
||||
class imageRatio:
|
||||
@@ -404,46 +432,6 @@ class imagePixelPerfect:
|
||||
|
||||
return {"ui": {"text": text}, "result": (result,)}
|
||||
|
||||
# 图片到遮罩
|
||||
class imageToMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"channel": (['red', 'green', 'blue'],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "convert"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
def convert_to_single_channel(self, image, channel='red'):
|
||||
# Convert to RGB mode to access individual channels
|
||||
image = image.convert('RGB')
|
||||
|
||||
# Extract the desired channel and convert to greyscale
|
||||
if channel == 'red':
|
||||
channel_img = image.split()[0].convert('L')
|
||||
elif channel == 'green':
|
||||
channel_img = image.split()[1].convert('L')
|
||||
elif channel == 'blue':
|
||||
channel_img = image.split()[2].convert('L')
|
||||
else:
|
||||
raise ValueError(
|
||||
"Invalid channel option. Please choose 'red', 'green', or 'blue'.")
|
||||
|
||||
# Convert the greyscale channel back to RGB mode
|
||||
channel_img = Image.merge(
|
||||
'RGB', (channel_img, channel_img, channel_img))
|
||||
|
||||
return channel_img
|
||||
|
||||
def convert(self, image, channel='red'):
|
||||
image = self.convert_to_single_channel(tensor2pil(image), channel)
|
||||
image = pil2tensor(image)
|
||||
return (image.squeeze().mean(2),)
|
||||
|
||||
# 图像保存 (简易)
|
||||
from nodes import PreviewImage, SaveImage
|
||||
class imageSaveSimple:
|
||||
@@ -477,7 +465,6 @@ class imageSaveSimple:
|
||||
else:
|
||||
return SaveImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
|
||||
|
||||
# 图像批次合并
|
||||
class JoinImageBatch:
|
||||
"""Turns an image batch into one big image."""
|
||||
@@ -525,13 +512,15 @@ class imageListToImageBatch:
|
||||
if len(images) <= 1:
|
||||
return (images[0],)
|
||||
else:
|
||||
image1 = images[0]
|
||||
for image2 in images[1:]:
|
||||
if image1.shape[1:] != image2.shape[1:]:
|
||||
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "lanczos",
|
||||
image_shape = images[0].shape
|
||||
for i, img in enumerate(images):
|
||||
if image_shape[1:] == img[1:]:
|
||||
continue
|
||||
else:
|
||||
images[i] = comfy.utils.common_upscale(img.movedim(-1, 1), img.shape[2], image_shape[1], "lanczos",
|
||||
"center").movedim(1, -1)
|
||||
image1 = torch.cat((image1, image2), dim=0)
|
||||
return (image1,)
|
||||
images = torch.cat(images, dim=0)
|
||||
return (images,)
|
||||
|
||||
|
||||
class imageBatchToImageList:
|
||||
@@ -619,6 +608,138 @@ class imageSplitGrid:
|
||||
|
||||
return (torch.cat(new_images, dim=0),)
|
||||
|
||||
class imageSplitTiles:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"overlap_ratio": ("FLOAT", {"default": 0, "min": 0, "max": 0.5, "step": 0.01, }),
|
||||
"overlap_offset": ("INT", {"default": 0, "min": - MAX_RESOLUTION // 2, "max": MAX_RESOLUTION // 2, "step": 1, }),
|
||||
"tiles_rows": ("INT", {"default": 2, "min": 1, "max": 50, "step": 1}),
|
||||
"tiles_cols": ("INT", {"default": 2, "min": 1, "max": 50, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"norm": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "OVERLAP", "INT")
|
||||
RETURN_NAMES = ("tiles", "masks", "overlap", "total")
|
||||
FUNCTION = "doit"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
def doit(self, image, overlap_ratio, overlap_offset, tiles_rows, tiles_cols, norm=True):
|
||||
height, width = image.shape[1:3]
|
||||
|
||||
total = tiles_rows * tiles_cols
|
||||
tile_w = int(width // tiles_cols)
|
||||
tile_h = int(height // tiles_rows)
|
||||
|
||||
overlap_w = int(tile_w * overlap_ratio) + overlap_offset
|
||||
overlap_h = int(tile_h * overlap_ratio) + overlap_offset
|
||||
|
||||
overlap_w = min(tile_w // 2, overlap_w)
|
||||
overlap_h = min(tile_h // 2, overlap_h)
|
||||
|
||||
if norm:
|
||||
overlap_w = int(overlap_w - overlap_w % 8)
|
||||
overlap_h = int(overlap_h - overlap_h % 8)
|
||||
|
||||
if tiles_rows == 1:
|
||||
overlap_h = 0
|
||||
if tiles_cols == 1:
|
||||
overlap_w = 0
|
||||
|
||||
solid_mask_cls = ALL_NODE_CLASS_MAPPINGS['SolidMask']
|
||||
feather_mask_cls = ALL_NODE_CLASS_MAPPINGS['FeatherMask']
|
||||
|
||||
tiles, masks = [], []
|
||||
|
||||
x, y = 0, 0
|
||||
for i in range(tiles_rows):
|
||||
for j in range(tiles_cols):
|
||||
y1 = i * tile_h
|
||||
x1 = j * tile_w
|
||||
|
||||
if i > 0:
|
||||
y1 -= overlap_h
|
||||
if j > 0:
|
||||
x1 -= overlap_w
|
||||
|
||||
y2 = y1 + tile_h + overlap_h
|
||||
x2 = x1 + tile_w + overlap_w
|
||||
|
||||
if y2 > height:
|
||||
y2 = height
|
||||
y1 = y2 - tile_h - overlap_h
|
||||
if x2 > width:
|
||||
x2 = width
|
||||
x1 = x2 - tile_w - overlap_w
|
||||
|
||||
tile = image[:, y1:y2, x1:x2, :]
|
||||
h = tile.shape[1]
|
||||
w = tile.shape[2]
|
||||
tiles.append(tile)
|
||||
|
||||
fearing_left = overlap_w if overlap_w * j > 0 else 0
|
||||
fearing_top = overlap_h if overlap_h * i > 0 else 0
|
||||
fearing_right = 0
|
||||
fearing_bottom = 0
|
||||
|
||||
mask, = solid_mask_cls().solid(1, w, h)
|
||||
mask, = feather_mask_cls().feather(mask, fearing_left, fearing_top, fearing_right, fearing_bottom)
|
||||
masks.append(mask)
|
||||
|
||||
tiles = torch.cat(tiles, dim=0)
|
||||
masks = torch.cat(masks, dim=0)
|
||||
|
||||
return (tiles, masks, (overlap_w, overlap_h, tile_w, tile_h, tiles_rows, tiles_cols), total)
|
||||
|
||||
class imageTilesFromBatch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"tiles": ("IMAGE",),
|
||||
"masks": ("MASK",),
|
||||
"overlap": ("OVERLAP",),
|
||||
"index":("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "INT", "INT")
|
||||
RETURN_NAMES = ("image", "mask", "x", "y")
|
||||
FUNCTION = "doit"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
def imageFromBatch(self, image, batch_index, length=1):
|
||||
s_in = image
|
||||
batch_index = min(s_in.shape[0] - 1, batch_index)
|
||||
length = min(s_in.shape[0] - batch_index, length)
|
||||
s = s_in[batch_index:batch_index + length].clone()
|
||||
return s
|
||||
|
||||
def maskFromBatch(self, mask, start, length=1):
|
||||
if length > mask.shape[0]:
|
||||
length = mask.shape[0]
|
||||
start = min(start, mask.shape[0]-1)
|
||||
length = min(mask.shape[0]-start, length)
|
||||
return mask[start:start + length]
|
||||
|
||||
def doit(self, tiles, masks, overlap, index):
|
||||
tile = self.imageFromBatch(tiles, index)
|
||||
mask = self.maskFromBatch(masks, index)
|
||||
overlap_w, overlap_h, tile_w, tile_h, tiles_rows, tiles_cols = overlap
|
||||
|
||||
x = tile_w * (index % tiles_cols) - overlap_w if (index % tiles_cols) > 0 else 0
|
||||
y = tile_h * (index // tiles_cols) - overlap_h if tiles_rows > 1 and index > tiles_cols - 1 else 0
|
||||
|
||||
return (tile, mask, x, y)
|
||||
|
||||
|
||||
|
||||
class imagesSplitImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -637,7 +758,6 @@ class imagesSplitImage:
|
||||
new_images = torch.chunk(images, len(images), dim=0)
|
||||
return new_images
|
||||
|
||||
|
||||
class imageConcat:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -653,6 +773,10 @@ class imageConcat:
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
def concat(self, image1, image2, direction, match_image_size):
|
||||
if image1 is None:
|
||||
return (image2,)
|
||||
elif image2 is None:
|
||||
return (image1,)
|
||||
if match_image_size:
|
||||
image2 = torch.nn.functional.interpolate(image2, size=(image1.shape[2], image1.shape[3]), mode="bilinear")
|
||||
if direction == 'right':
|
||||
@@ -674,7 +798,7 @@ class imageRemBg:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"rem_mode": (("RMBG-1.4","Inspyrenet"),),
|
||||
"rem_mode": (("RMBG-2.0", "RMBG-1.4","Inspyrenet"), {"default": "RMBG-1.4"}),
|
||||
"image_output": (["Hide", "Preview", "Save", "Hide/Save"], {"default": "Preview"}),
|
||||
"save_prefix": ("STRING", {"default": "ComfyUI"}),
|
||||
|
||||
@@ -695,7 +819,50 @@ class imageRemBg:
|
||||
def remove(self, rem_mode, images, image_output, save_prefix, torchscript_jit=False, prompt=None, extra_pnginfo=None):
|
||||
new_images = list()
|
||||
masks = list()
|
||||
if rem_mode == "RMBG-1.4":
|
||||
if rem_mode == "RMBG-2.0":
|
||||
repo_id = REMBG_MODELS[rem_mode]['model_url']
|
||||
model_path = os.path.join(REMBG_DIR, 'RMBG-2.0')
|
||||
if not os.path.exists(model_path):
|
||||
from huggingface_hub import snapshot_download
|
||||
snapshot_download(repo_id=repo_id, local_dir=model_path, ignore_patterns=["*.md", "*.txt"])
|
||||
from transformers import AutoModelForImageSegmentation
|
||||
model = AutoModelForImageSegmentation.from_pretrained(model_path, trust_remote_code=True)
|
||||
torch.set_float32_matmul_precision('high')
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
model.to(device)
|
||||
model.eval()
|
||||
|
||||
from torchvision import transforms
|
||||
transform_image = transforms.Compose([
|
||||
transforms.Resize((1024, 1024)),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
||||
])
|
||||
for image in images:
|
||||
orig_im = tensor2pil(image)
|
||||
input_tensor = transform_image(orig_im).unsqueeze(0).to(device)
|
||||
|
||||
with torch.no_grad():
|
||||
preds = model(input_tensor)[-1].sigmoid().cpu()
|
||||
pred = preds[0].squeeze()
|
||||
|
||||
mask = transforms.ToPILImage()(pred)
|
||||
mask = mask.resize(orig_im.size)
|
||||
|
||||
new_im = orig_im.copy()
|
||||
new_im.putalpha(mask)
|
||||
|
||||
new_im_tensor = pil2tensor(new_im)
|
||||
mask_tensor = pil2tensor(mask)
|
||||
|
||||
new_images.append(new_im_tensor)
|
||||
masks.append(mask_tensor)
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
new_images = torch.cat(new_images, dim=0)
|
||||
masks = torch.cat(masks, dim=0)
|
||||
|
||||
elif rem_mode == "RMBG-1.4":
|
||||
# load model
|
||||
model_url = REMBG_MODELS[rem_mode]['model_url']
|
||||
suffix = model_url.split(".")[-1]
|
||||
@@ -926,13 +1093,17 @@ class imageDetailTransfer:
|
||||
|
||||
|
||||
def transfer(self, target, source, mode, blur_sigma, blend_factor, image_output, save_prefix, mask=None, prompt=None, extra_pnginfo=None):
|
||||
batch_size, height, width, _ = target.shape
|
||||
batch_size, height, width, _ = source.shape
|
||||
device = comfy.model_management.get_torch_device()
|
||||
target_tensor = target.permute(0, 3, 1, 2).clone().to(device)
|
||||
source_tensor = source.permute(0, 3, 1, 2).clone().to(device)
|
||||
|
||||
if target.shape[1:] != source.shape[1:]:
|
||||
source_tensor = comfy.utils.common_upscale(source_tensor, width, height, "bilinear", "disabled")
|
||||
target_tensor = comfy.utils.common_upscale(target_tensor, width, height, "bilinear", "disabled")
|
||||
if mask is not None and target.shape[1:] != mask.shape[1:]:
|
||||
mask = mask.unsqueeze(1)
|
||||
mask = F.interpolate(mask, size=(height, width), mode="bilinear")
|
||||
mask = mask.squeeze(1)
|
||||
|
||||
if source.shape[0] < batch_size:
|
||||
source = source[0].unsqueeze(0).repeat(batch_size, 1, 1, 1)
|
||||
@@ -1004,12 +1175,12 @@ class imageInterrogator:
|
||||
RETURN_NAMES = ("prompt",)
|
||||
FUNCTION = "interrogate"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_NODE = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def interrogate(self, image, mode, use_lowvram=False):
|
||||
prompt = ci.image_to_prompt(image, mode, low_vram=use_lowvram)
|
||||
return {"ui":{"text":prompt},"result":(prompt,)}
|
||||
return (prompt,)
|
||||
|
||||
# 人类分割器
|
||||
class humanSegmentation:
|
||||
@@ -1020,7 +1191,7 @@ class humanSegmentation:
|
||||
return {
|
||||
"required":{
|
||||
"image": ("IMAGE",),
|
||||
"method": (["selfie_multiclass_256x256", "human_parsing_lip"],),
|
||||
"method": (["selfie_multiclass_256x256", "human_parsing_lip", "human_parts (deeplabv3p)"],),
|
||||
"confidence": ("FLOAT", {"default": 0.4, "min": 0.05, "max": 0.95, "step": 0.01},),
|
||||
"crop_multi": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001},),
|
||||
},
|
||||
@@ -1148,6 +1319,26 @@ class humanSegmentation:
|
||||
|
||||
output_image, = JoinImageWithAlpha().join_image_with_alpha(image, alpha)
|
||||
|
||||
elif method == "human_parts (deeplabv3p)":
|
||||
from .human_parsing.run_parsing import HumanParts
|
||||
onnx_path = os.path.join(folder_paths.models_dir, 'onnx')
|
||||
human_parts_path = os.path.join(onnx_path, 'human-parts')
|
||||
model_path = get_local_filepath(HUMANPARSING_MODELS['human-parts']['model_url'], human_parts_path)
|
||||
parsing = HumanParts(model_path=model_path)
|
||||
|
||||
ret_images = []
|
||||
ret_masks = []
|
||||
for img in image:
|
||||
mask, = parsing(img, mask_components)
|
||||
_mask = tensor2pil(mask).convert('L')
|
||||
|
||||
ret_image = RGB2RGBA(tensor2pil(img).convert('RGB'), _mask.convert('L'))
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
ret_masks.append(image2mask(_mask))
|
||||
|
||||
output_image = torch.cat(ret_images, dim=0)
|
||||
mask = torch.cat(ret_masks, dim=0)
|
||||
|
||||
# use crop
|
||||
bbox = [[0, 0, 0, 0]]
|
||||
if crop_multi > 0.0:
|
||||
@@ -1155,7 +1346,6 @@ class humanSegmentation:
|
||||
|
||||
return (output_image, mask, bbox)
|
||||
|
||||
|
||||
class imageCropFromMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1485,7 +1675,7 @@ class removeLocalImage:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"any": (AlwaysEqualProxy("*"),),
|
||||
"any": (any_type,),
|
||||
"file_name": ("STRING",{"default":""}),
|
||||
},
|
||||
}
|
||||
@@ -1521,48 +1711,302 @@ class removeLocalImage:
|
||||
PromptServer.instance.send_sync("easyuse-toast", {"content": "Removed Failed", "type": 'error'})
|
||||
return ()
|
||||
|
||||
|
||||
# 姿势编辑器
|
||||
class poseEditor:
|
||||
try:
|
||||
from comfy_execution.graph_utils import GraphBuilder, is_link
|
||||
except:
|
||||
GraphBuilder = None
|
||||
class loadImagesForLoop:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
temp_dir = folder_paths.get_temp_directory()
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"directory": ("STRING", {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"limit": ("INT", {"default":-1, "min":-1, "max": 10000}),
|
||||
"initial_value1": (any_type,),
|
||||
"initial_value2": (any_type,),
|
||||
},
|
||||
"hidden": {
|
||||
"initial_value0": (any_type,),
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
"unique_id": "UNIQUE_ID"
|
||||
}
|
||||
}
|
||||
|
||||
if not os.path.isdir(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
RETURN_TYPES = ByPassTypeTuple(tuple(["FLOW_CONTROL", "INT", "IMAGE", "MASK", "STRING", any_type, any_type]))
|
||||
RETURN_NAMES = ByPassTypeTuple(tuple(["flow", "index", "image", "mask", "name", "value1", "value2"]))
|
||||
|
||||
temp_dir = folder_paths.get_temp_directory()
|
||||
FUNCTION = "load_images"
|
||||
|
||||
return {"required":
|
||||
{"image": (sorted(os.listdir(temp_dir)),)},
|
||||
}
|
||||
CATEGORY = "image"
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "output_pose"
|
||||
def load_images(self, directory: str, start_index: int = 0, limit: int =-1, prompt=None, extra_pnginfo=None, unique_id=None, **kwargs):
|
||||
if not os.path.isdir(directory):
|
||||
raise FileNotFoundError(f"Directory '{directory}' cannot be found.")
|
||||
dir_files = os.listdir(directory)
|
||||
if len(dir_files) == 0:
|
||||
raise FileNotFoundError(f"No files in directory '{directory}'.")
|
||||
|
||||
CATEGORY = "EasyUse/Image"
|
||||
# Filter files by extension
|
||||
valid_extensions = ['.jpg', '.jpeg', '.png', '.webp']
|
||||
dir_files = [f for f in dir_files if any(f.lower().endswith(ext) for ext in valid_extensions)]
|
||||
|
||||
def output_pose(self, image):
|
||||
image_path = os.path.join(folder_paths.get_temp_directory(), image)
|
||||
# print(f"Create: {image_path}")
|
||||
dir_files = sorted(dir_files)
|
||||
dir_files = [os.path.join(directory, x) for x in dir_files]
|
||||
|
||||
graph = GraphBuilder()
|
||||
index = 0
|
||||
# unique_id = unique_id.split('.')[len(unique_id.split('.')) - 1] if "." in unique_id else unique_id
|
||||
# update_cache('forloop' + str(unique_id), 'forloop', total)
|
||||
if "initial_value0" in kwargs:
|
||||
index = kwargs["initial_value0"]
|
||||
# start at start_index
|
||||
image_path = dir_files[start_index+index]
|
||||
|
||||
name = os.path.splitext(os.path.basename(image_path))[0]
|
||||
|
||||
i = Image.open(image_path)
|
||||
i = ImageOps.exif_transpose(i)
|
||||
image = i.convert("RGB")
|
||||
image = np.array(image).astype(np.float32) / 255.0
|
||||
image = torch.from_numpy(image)[None,]
|
||||
|
||||
return (image,)
|
||||
if 'A' in i.getbands():
|
||||
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
|
||||
mask = 1. - torch.from_numpy(mask)
|
||||
else:
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
|
||||
while_open = graph.node("easy whileLoopStart", condition=True, initial_value0=index, initial_value1=kwargs.get('initial_value1',None), initial_value2=kwargs.get('initial_value2',None))
|
||||
outputs = [kwargs.get('initial_value1',None), kwargs.get('initial_value2',None)]
|
||||
|
||||
return {
|
||||
"result": tuple(["stub", index, image, mask, name] + outputs),
|
||||
"expand": graph.finalize(),
|
||||
}
|
||||
# 姿势编辑器
|
||||
# class poseEditor:
|
||||
# @classmethod
|
||||
# def INPUT_TYPES(self):
|
||||
# temp_dir = folder_paths.get_temp_directory()
|
||||
#
|
||||
# if not os.path.isdir(temp_dir):
|
||||
# os.makedirs(temp_dir)
|
||||
#
|
||||
# temp_dir = folder_paths.get_temp_directory()
|
||||
#
|
||||
# return {"required":
|
||||
# {"image": (sorted(os.listdir(temp_dir)),)},
|
||||
# }
|
||||
#
|
||||
# RETURN_TYPES = ("IMAGE",)
|
||||
# FUNCTION = "output_pose"
|
||||
#
|
||||
# CATEGORY = "EasyUse/🚫 Deprecated"
|
||||
#
|
||||
# def output_pose(self, image):
|
||||
# image_path = os.path.join(folder_paths.get_temp_directory(), image)
|
||||
# # print(f"Create: {image_path}")
|
||||
#
|
||||
# i = Image.open(image_path)
|
||||
# image = i.convert("RGB")
|
||||
# image = np.array(image).astype(np.float32) / 255.0
|
||||
# image = torch.from_numpy(image)[None,]
|
||||
#
|
||||
# return (image,)
|
||||
#
|
||||
# @classmethod
|
||||
# def IS_CHANGED(self, image):
|
||||
# image_path = os.path.join(
|
||||
# folder_paths.get_temp_directory(), image)
|
||||
# # print(f'Change: {image_path}')
|
||||
#
|
||||
# m = hashlib.sha256()
|
||||
# with open(image_path, 'rb') as f:
|
||||
# m.update(f.read())
|
||||
# return m.digest().hex()
|
||||
|
||||
class saveImageLazy():
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
self.type = "output"
|
||||
self.compress_level = 4
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(self, image):
|
||||
image_path = os.path.join(
|
||||
folder_paths.get_temp_directory(), image)
|
||||
# print(f'Change: {image_path}')
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"images": ("IMAGE",),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI"}),
|
||||
"save_metadata": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
"optional":{},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("images",)
|
||||
OUTPUT_NODE = False
|
||||
FUNCTION = "save"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
def save(self, images, filename_prefix, save_metadata, prompt=None, extra_pnginfo=None):
|
||||
extension = 'png'
|
||||
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
|
||||
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||||
|
||||
results = list()
|
||||
for (batch_number, image) in enumerate(images):
|
||||
i = 255. * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
metadata = None
|
||||
|
||||
filename_with_batch_num = filename.replace(
|
||||
"%batch_num%", str(batch_number))
|
||||
|
||||
counter = 1
|
||||
|
||||
if os.path.exists(full_output_folder) and os.listdir(full_output_folder):
|
||||
filtered_filenames = list(filter(
|
||||
lambda filename: filename.startswith(
|
||||
filename_with_batch_num + "_")
|
||||
and filename[len(filename_with_batch_num) + 1:-4].isdigit(),
|
||||
os.listdir(full_output_folder)
|
||||
))
|
||||
|
||||
if filtered_filenames:
|
||||
max_counter = max(
|
||||
int(filename[len(filename_with_batch_num) + 1:-4])
|
||||
for filename in filtered_filenames
|
||||
)
|
||||
counter = max_counter + 1
|
||||
|
||||
file = f"{filename_with_batch_num}_{counter:05}.{extension}"
|
||||
|
||||
save_path = os.path.join(full_output_folder, file)
|
||||
|
||||
if save_metadata:
|
||||
metadata = PngInfo()
|
||||
if prompt is not None:
|
||||
metadata.add_text("prompt", json.dumps(prompt))
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
metadata.add_text(
|
||||
x, json.dumps(extra_pnginfo[x]))
|
||||
|
||||
img.save(save_path, pnginfo=metadata)
|
||||
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": self.type
|
||||
})
|
||||
|
||||
return {"ui": {"images": results} , "result": (images,)}
|
||||
|
||||
|
||||
class makeImageForICRepaint:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image_1": ("IMAGE",),
|
||||
"direction": (["top-bottom", "left-right"], {"default": "left-right"}),
|
||||
"pixels": ("INT", {"default": 0, "max": MAX_RESOLUTION, "min": 0, "step": 8, "tooltip": "The pixel of the output image is not set when it is 0"}),
|
||||
},
|
||||
"optional": {
|
||||
"image_2": ("IMAGE",),
|
||||
"mask_1": ("MASK",),
|
||||
"mask_2": ("MASK",),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "make Image for ICLora to Re-paint"
|
||||
CATEGORY = "EasyUse/Image"
|
||||
FUNCTION = "make"
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "MASK", "INT", "INT", "INT", "INT")
|
||||
RETURN_NAMES = ("image", "mask", "context_mask", "width", "height", "x", "y")
|
||||
|
||||
def fillMask(self, width, height, mask, box=(0, 0), color=0):
|
||||
bg = Image.new("L", (width, height), color)
|
||||
bg.paste(mask, box, mask)
|
||||
return bg
|
||||
|
||||
def emptyImage(self, width, height, batch_size=1, color=0):
|
||||
r = torch.full([batch_size, height, width, 1], ((color >> 16) & 0xFF) / 0xFF)
|
||||
g = torch.full([batch_size, height, width, 1], ((color >> 8) & 0xFF) / 0xFF)
|
||||
b = torch.full([batch_size, height, width, 1], ((color) & 0xFF) / 0xFF)
|
||||
return torch.cat((r, g, b), dim=-1)
|
||||
|
||||
def make(self, image_1, direction, pixels=0, image_2=None, mask_1=None, mask_2=None):
|
||||
if image_2 is None:
|
||||
image_2 = self.emptyImage(image_1.shape[2], image_1.shape[1])
|
||||
mask_2 = torch.full((1, image_1.shape[1], image_1.shape[2]), 1, dtype=torch.float32, device="cpu")
|
||||
|
||||
elif image_2 is not None and mask_2 is None:
|
||||
raise ValueError("mask_2 is required when image_2 is provided")
|
||||
if pixels > 0:
|
||||
_, img2_h, img2_w, _ = image_2.shape
|
||||
h = pixels if direction == 'left-right' else int(img2_h * (pixels / img2_w))
|
||||
w = pixels if direction == 'top-bottom' else int(img2_w * (pixels / img2_h))
|
||||
|
||||
image_2 = image_2.movedim(-1, 1)
|
||||
image_2 = comfy.utils.common_upscale(image_2, w, h, 'bicubic', 'disabled')
|
||||
image_2 = image_2.movedim(1, -1)
|
||||
|
||||
orig_image_2 = tensor2pil(image_2)
|
||||
orig_mask_2 = tensor2pil(mask_2).convert('L')
|
||||
orig_mask_2 = orig_mask_2.resize(orig_image_2.size)
|
||||
mask_2 = pil2tensor(orig_mask_2)
|
||||
|
||||
_, img1_h, img1_w, _ = image_1.shape
|
||||
_, img2_h, img2_w, _ = image_2.shape
|
||||
|
||||
image, mask, context_mask = None, None, None
|
||||
|
||||
# resize
|
||||
if img1_h != img2_h and img1_w != img2_w:
|
||||
width, height = img2_w, img2_h
|
||||
if direction == 'left-right' and img1_h != img2_h:
|
||||
scale_factor = img2_h / img1_h
|
||||
width = round(img1_w * scale_factor)
|
||||
elif direction == 'top-bottom' and img1_w != img2_w:
|
||||
scale_factor = img2_w / img1_w
|
||||
height = round(img1_h * scale_factor)
|
||||
|
||||
image_1 = image_1.movedim(-1, 1)
|
||||
image_1 = comfy.utils.common_upscale(image_1, width, height, 'bicubic', 'disabled')
|
||||
image_1 = image_1.movedim(1, -1)
|
||||
|
||||
if mask_1 is None:
|
||||
mask_1 = torch.full((1, image_1.shape[1], image_1.shape[2]), 0, dtype=torch.float32, device="cpu")
|
||||
|
||||
orig_image_1 = tensor2pil(image_1)
|
||||
orig_mask_1 = tensor2pil(mask_1).convert('L')
|
||||
|
||||
if orig_mask_1.size != orig_image_1.size:
|
||||
orig_mask_1 = orig_mask_1.resize(orig_image_1.size)
|
||||
|
||||
img1_w, img1_h = orig_image_1.size
|
||||
image_1 = pil2tensor(orig_image_1)
|
||||
image = torch.cat((image_1, image_2), dim=2) if direction == 'left-right' else torch.cat((image_1, image_2),
|
||||
dim=1)
|
||||
|
||||
context_mask = self.fillMask(image.shape[2], image.shape[1], orig_mask_1)
|
||||
context_mask = pil2tensor(context_mask)
|
||||
|
||||
orig_mask_2 = tensor2pil(mask_2).convert('L')
|
||||
x = img1_w if direction == 'left-right' else 0
|
||||
y = img1_h if direction == 'top-bottom' else 0
|
||||
mask = self.fillMask(image.shape[2], image.shape[1], orig_mask_2, (x, y))
|
||||
mask = pil2tensor(mask)
|
||||
|
||||
return (image, mask, context_mask, img2_w, img2_h, x, y)
|
||||
|
||||
m = hashlib.sha256()
|
||||
with open(image_path, 'rb') as f:
|
||||
m.update(f.read())
|
||||
return m.digest().hex()
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"easy imageInsetCrop": imageInsetCrop,
|
||||
@@ -1574,14 +2018,16 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy imageScaleDown": imageScaleDown,
|
||||
"easy imageScaleDownBy": imageScaleDownBy,
|
||||
"easy imageScaleDownToSize": imageScaleDownToSize,
|
||||
"easy imageScaleToNormPixels": imageScaleToNormPixels,
|
||||
"easy imageRatio": imageRatio,
|
||||
"easy imageToMask": imageToMask,
|
||||
"easy imageConcat": imageConcat,
|
||||
"easy imageListToImageBatch": imageListToImageBatch,
|
||||
"easy imageBatchToImageList": imageBatchToImageList,
|
||||
"easy imageSplitList": imageSplitList,
|
||||
"easy imageSplitGrid": imageSplitGrid,
|
||||
"easy imagesSplitImage": imagesSplitImage,
|
||||
"easy imageSplitTiles": imageSplitTiles,
|
||||
"easy imageTilesFromBatch": imageTilesFromBatch,
|
||||
"easy imageCropFromMask": imageCropFromMask,
|
||||
"easy imageUncropFromBBOX": imageUncropFromBBOX,
|
||||
"easy imageSave": imageSaveSimple,
|
||||
@@ -1590,12 +2036,14 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy imageColorMatch": imageColorMatch,
|
||||
"easy imageDetailTransfer": imageDetailTransfer,
|
||||
"easy imageInterrogator": imageInterrogator,
|
||||
"easy loadImagesForLoop": loadImagesForLoop,
|
||||
"easy loadImageBase64": loadImageBase64,
|
||||
"easy imageToBase64": imageToBase64,
|
||||
"easy joinImageBatch": JoinImageBatch,
|
||||
"easy humanSegmentation": humanSegmentation,
|
||||
"easy removeLocalImage": removeLocalImage,
|
||||
"easy poseEditor": poseEditor
|
||||
"easy saveImageLazy": saveImageLazy,
|
||||
"easy makeImageForICLora": makeImageForICRepaint
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -1608,18 +2056,20 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy imageScaleDown": "Image Scale Down",
|
||||
"easy imageScaleDownBy": "Image Scale Down By",
|
||||
"easy imageScaleDownToSize": "Image Scale Down To Size",
|
||||
"easy imageScaleToNormPixels": "ImageScaleToNormPixels",
|
||||
"easy imageRatio": "ImageRatio",
|
||||
"easy imageToMask": "ImageToMask",
|
||||
"easy imageHSVMask": "ImageHSVMask",
|
||||
"easy imageConcat": "imageConcat",
|
||||
"easy imageListToImageBatch": "Image List To Image Batch",
|
||||
"easy imageBatchToImageList": "Image Batch To Image List",
|
||||
"easy imageSplitList": "imageSplitList",
|
||||
"easy imageSplitGrid": "imageSplitGrid",
|
||||
"easy imageSplitTiles": "imageSplitTiles",
|
||||
"easy imageTilesFromBatch": "imageTilesFromBatch",
|
||||
"easy imagesSplitImage": "imagesSplitImage",
|
||||
"easy imageCropFromMask": "imageCropFromMask",
|
||||
"easy imageUncropFromBBOX": "imageUncropFromBBOX",
|
||||
"easy imageSave": "SaveImage (Simple)",
|
||||
"easy imageSave": "Save Image (Simple)",
|
||||
"easy imageRemBg": "Image Remove Bg",
|
||||
"easy imageChooser": "Image Chooser",
|
||||
"easy imageColorMatch": "Image Color Match",
|
||||
@@ -1627,8 +2077,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy imageInterrogator": "Image To Prompt",
|
||||
"easy joinImageBatch": "JoinImageBatch",
|
||||
"easy loadImageBase64": "Load Image (Base64)",
|
||||
"easy loadImagesForLoop": "Load Images For Loop",
|
||||
"easy imageToBase64": "Image To Base64",
|
||||
"easy humanSegmentation": "Human Segmentation",
|
||||
"easy removeLocalImage": "Remove Local Image",
|
||||
"easy poseEditor": "PoseEditor",
|
||||
"easy saveImageLazy": "Save Image (Lazy)",
|
||||
"easy makeImageForICLora": "Make Image For ICLora"
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
#credit to shakker-labs and instantX for this module
|
||||
#from https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux
|
||||
import torch
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from .attention_processor import IPAFluxAttnProcessor2_0
|
||||
from .utils import is_model_pathched, FluxUpdateModules
|
||||
from .sd3.resampler import TimeResampler
|
||||
from .sd3.joinblock import JointBlockIPWrapper, IPAttnProcessor
|
||||
|
||||
image_proj_model = None
|
||||
class MLPProjModel(torch.nn.Module):
|
||||
def __init__(self, cross_attention_dim=768, id_embeddings_dim=512, num_tokens=4):
|
||||
super().__init__()
|
||||
|
||||
self.cross_attention_dim = cross_attention_dim
|
||||
self.num_tokens = num_tokens
|
||||
|
||||
self.proj = torch.nn.Sequential(
|
||||
torch.nn.Linear(id_embeddings_dim, id_embeddings_dim * 2),
|
||||
torch.nn.GELU(),
|
||||
torch.nn.Linear(id_embeddings_dim * 2, cross_attention_dim * num_tokens),
|
||||
)
|
||||
self.norm = torch.nn.LayerNorm(cross_attention_dim)
|
||||
|
||||
def forward(self, id_embeds):
|
||||
x = self.proj(id_embeds)
|
||||
x = x.reshape(-1, self.num_tokens, self.cross_attention_dim)
|
||||
x = self.norm(x)
|
||||
return x
|
||||
|
||||
class InstantXFluxIpadapterApply:
|
||||
def __init__(self, num_tokens=128):
|
||||
self.device = None
|
||||
self.dtype = torch.float16
|
||||
self.num_tokens = num_tokens
|
||||
self.ip_ckpt = None
|
||||
self.clip_vision = None
|
||||
self.image_encoder = None
|
||||
self.clip_image_processor = None
|
||||
# state_dict
|
||||
self.state_dict = None
|
||||
self.joint_attention_dim = 4096
|
||||
self.hidden_size = 3072
|
||||
|
||||
def set_ip_adapter(self, flux_model, weight, timestep_percent_range=(0.0, 1.0)):
|
||||
s = flux_model.model_sampling
|
||||
percent_to_timestep_function = lambda a: s.percent_to_sigma(a)
|
||||
timestep_range = (percent_to_timestep_function(timestep_percent_range[0]),
|
||||
percent_to_timestep_function(timestep_percent_range[1]))
|
||||
ip_attn_procs = {} # 19+38=57
|
||||
dsb_count = len(flux_model.diffusion_model.double_blocks)
|
||||
for i in range(dsb_count):
|
||||
name = f"double_blocks.{i}"
|
||||
ip_attn_procs[name] = IPAFluxAttnProcessor2_0(
|
||||
hidden_size=self.hidden_size,
|
||||
cross_attention_dim=self.joint_attention_dim,
|
||||
num_tokens=self.num_tokens,
|
||||
scale=weight,
|
||||
timestep_range=timestep_range
|
||||
).to(self.device, dtype=self.dtype)
|
||||
ssb_count = len(flux_model.diffusion_model.single_blocks)
|
||||
for i in range(ssb_count):
|
||||
name = f"single_blocks.{i}"
|
||||
ip_attn_procs[name] = IPAFluxAttnProcessor2_0(
|
||||
hidden_size=self.hidden_size,
|
||||
cross_attention_dim=self.joint_attention_dim,
|
||||
num_tokens=self.num_tokens,
|
||||
scale=weight,
|
||||
timestep_range=timestep_range
|
||||
).to(self.device, dtype=self.dtype)
|
||||
return ip_attn_procs
|
||||
|
||||
def load_ip_adapter(self, flux_model, weight, timestep_percent_range=(0.0, 1.0)):
|
||||
global image_proj_model
|
||||
image_proj_model.load_state_dict(self.state_dict["image_proj"], strict=True)
|
||||
ip_attn_procs = self.set_ip_adapter(flux_model, weight, timestep_percent_range)
|
||||
ip_layers = torch.nn.ModuleList(ip_attn_procs.values())
|
||||
ip_layers.load_state_dict(self.state_dict["ip_adapter"], strict=True)
|
||||
return ip_attn_procs
|
||||
|
||||
def get_image_embeds(self, pil_image=None, clip_image_embeds=None):
|
||||
# outputs = self.clip_vision.encode_image(pil_image)
|
||||
# clip_image_embeds = outputs['image_embeds']
|
||||
# clip_image_embeds = clip_image_embeds.to(self.device, dtype=self.dtype)
|
||||
# image_prompt_embeds = self.image_proj_model(clip_image_embeds)
|
||||
if pil_image is not None:
|
||||
if isinstance(pil_image, Image.Image):
|
||||
pil_image = [pil_image]
|
||||
clip_image = self.clip_image_processor(images=pil_image, return_tensors="pt").pixel_values
|
||||
clip_image_embeds = self.image_encoder(
|
||||
clip_image.to(self.device, dtype=self.image_encoder.dtype)).pooler_output
|
||||
clip_image_embeds = clip_image_embeds.to(dtype=self.dtype)
|
||||
else:
|
||||
clip_image_embeds = clip_image_embeds.to(self.device, dtype=self.dtype)
|
||||
global image_proj_model
|
||||
image_prompt_embeds = image_proj_model(clip_image_embeds)
|
||||
return image_prompt_embeds
|
||||
|
||||
def apply_ipadapter(self, model, ipadapter, image, weight, start_at, end_at, provider=None, use_tiled=False):
|
||||
self.device = provider.lower()
|
||||
if "clipvision" in ipadapter:
|
||||
# self.clip_vision = ipadapter["clipvision"]['model']
|
||||
self.image_encoder = ipadapter["clipvision"]['model']['image_encoder'].to(self.device, dtype=self.dtype)
|
||||
self.clip_image_processor = ipadapter["clipvision"]['model']['clip_image_processor']
|
||||
if "ipadapter" in ipadapter:
|
||||
self.ip_ckpt = ipadapter["ipadapter"]['file']
|
||||
self.state_dict = ipadapter["ipadapter"]['model']
|
||||
|
||||
# process image
|
||||
pil_image = image.numpy()[0] * 255.0
|
||||
pil_image = Image.fromarray(pil_image.astype(np.uint8))
|
||||
# initialize ipadapter
|
||||
global image_proj_model
|
||||
if image_proj_model is None:
|
||||
image_proj_model = MLPProjModel(
|
||||
cross_attention_dim=self.joint_attention_dim, # 4096
|
||||
id_embeddings_dim=1152,
|
||||
num_tokens=self.num_tokens,
|
||||
)
|
||||
image_proj_model.to(self.device, dtype=self.dtype)
|
||||
ip_attn_procs = self.load_ip_adapter(model.model, weight, (start_at, end_at))
|
||||
# process control image
|
||||
image_prompt_embeds = self.get_image_embeds(pil_image=pil_image, clip_image_embeds=None)
|
||||
# set model
|
||||
is_patched = is_model_pathched(model.model)
|
||||
bi = model.clone()
|
||||
FluxUpdateModules(bi, ip_attn_procs, image_prompt_embeds, is_patched)
|
||||
|
||||
return (bi, image)
|
||||
|
||||
|
||||
def patch_sd3(
|
||||
patcher,
|
||||
ip_procs,
|
||||
resampler: TimeResampler,
|
||||
clip_embeds,
|
||||
weight=1.0,
|
||||
start=0.0,
|
||||
end=1.0,
|
||||
):
|
||||
"""
|
||||
Patches a model_sampler to add the ipadapter
|
||||
"""
|
||||
mmdit = patcher.model.diffusion_model
|
||||
timestep_schedule_max = patcher.model.model_config.sampling_settings.get(
|
||||
"timesteps", 1000
|
||||
)
|
||||
# hook the model's forward function
|
||||
# so that when it gets called, we can grab the timestep and send it to the resampler
|
||||
ip_options = {
|
||||
"hidden_states": None,
|
||||
"t_emb": None,
|
||||
"weight": weight,
|
||||
}
|
||||
|
||||
def ddit_wrapper(forward, args):
|
||||
# this is between 0 and 1, so the adapters can calculate start_point and end_point
|
||||
# actually, do we need to get the sigma value instead?
|
||||
t_percent = 1 - args["timestep"].flatten()[0].cpu().item()
|
||||
if start <= t_percent <= end:
|
||||
batch_size = args["input"].shape[0] // len(args["cond_or_uncond"])
|
||||
# if we're only doing cond or only doing uncond, only pass one of them through the resampler
|
||||
embeds = clip_embeds[args["cond_or_uncond"]]
|
||||
# slight efficiency optimization todo: pass the embeds through and then afterwards
|
||||
# repeat to the batch size
|
||||
embeds = torch.repeat_interleave(embeds, batch_size, dim=0)
|
||||
# the resampler wants between 0 and MAX_STEPS
|
||||
timestep = args["timestep"] * timestep_schedule_max
|
||||
image_emb, t_emb = resampler(embeds, timestep, need_temb=True)
|
||||
# these will need to be accessible to the IPAdapters
|
||||
ip_options["hidden_states"] = image_emb
|
||||
ip_options["t_emb"] = t_emb
|
||||
else:
|
||||
ip_options["hidden_states"] = None
|
||||
ip_options["t_emb"] = None
|
||||
|
||||
return forward(args["input"], args["timestep"], **args["c"])
|
||||
|
||||
patcher.set_model_unet_function_wrapper(ddit_wrapper)
|
||||
# patch each dit block
|
||||
for i, block in enumerate(mmdit.joint_blocks):
|
||||
wrapper = JointBlockIPWrapper(block, ip_procs[i], ip_options)
|
||||
patcher.set_model_patch_replace(wrapper, "dit", "double_block", i)
|
||||
|
||||
class InstantXSD3IpadapterApply:
|
||||
def __init__(self):
|
||||
self.device = None
|
||||
self.dtype = torch.float16
|
||||
self.clip_image_processor = None
|
||||
self.image_encoder = None
|
||||
self.resampler = None
|
||||
self.procs = None
|
||||
|
||||
@torch.inference_mode()
|
||||
def encode(self, image):
|
||||
clip_image = self.clip_image_processor.image_processor(image, return_tensors="pt", do_rescale=False).pixel_values
|
||||
clip_image_embeds = self.image_encoder(
|
||||
clip_image.to(self.device, dtype=self.image_encoder.dtype),
|
||||
output_hidden_states=True,
|
||||
).hidden_states[-2]
|
||||
clip_image_embeds = torch.cat(
|
||||
[clip_image_embeds, torch.zeros_like(clip_image_embeds)], dim=0
|
||||
)
|
||||
clip_image_embeds = clip_image_embeds.to(dtype=torch.float16)
|
||||
return clip_image_embeds
|
||||
|
||||
def apply_ipadapter(self, model, ipadapter, image, weight, start_at, end_at, provider=None, use_tiled=False):
|
||||
self.device = provider.lower()
|
||||
if "clipvision" in ipadapter:
|
||||
self.image_encoder = ipadapter["clipvision"]['model']['image_encoder'].to(self.device, dtype=self.dtype)
|
||||
self.clip_image_processor = ipadapter["clipvision"]['model']['clip_image_processor']
|
||||
if "ipadapter" in ipadapter:
|
||||
self.ip_ckpt = ipadapter["ipadapter"]['file']
|
||||
self.state_dict = ipadapter["ipadapter"]['model']
|
||||
|
||||
self.resampler = TimeResampler(
|
||||
dim=1280,
|
||||
depth=4,
|
||||
dim_head=64,
|
||||
heads=20,
|
||||
num_queries=64,
|
||||
embedding_dim=1152,
|
||||
output_dim=2432,
|
||||
ff_mult=4,
|
||||
timestep_in_dim=320,
|
||||
timestep_flip_sin_to_cos=True,
|
||||
timestep_freq_shift=0,
|
||||
)
|
||||
self.resampler.eval()
|
||||
self.resampler.to(self.device, dtype=self.dtype)
|
||||
self.resampler.load_state_dict(self.state_dict["image_proj"])
|
||||
|
||||
# now we'll create the attention processors
|
||||
# ip_adapter.keys looks like [0.proj, 0.to_k, ..., 1.proj, 1.to_k, ...]
|
||||
n_procs = len(
|
||||
set(x.split(".")[0] for x in self.state_dict["ip_adapter"].keys())
|
||||
)
|
||||
self.procs = torch.nn.ModuleList(
|
||||
[
|
||||
# this is hardcoded for SD3.5L
|
||||
IPAttnProcessor(
|
||||
hidden_size=2432,
|
||||
cross_attention_dim=2432,
|
||||
ip_hidden_states_dim=2432,
|
||||
ip_encoder_hidden_states_dim=2432,
|
||||
head_dim=64,
|
||||
timesteps_emb_dim=1280,
|
||||
).to(self.device, dtype=torch.float16)
|
||||
for _ in range(n_procs)
|
||||
]
|
||||
)
|
||||
self.procs.load_state_dict(self.state_dict["ip_adapter"])
|
||||
|
||||
work_model = model.clone()
|
||||
embeds = self.encode(image)
|
||||
|
||||
patch_sd3(
|
||||
work_model,
|
||||
self.procs,
|
||||
self.resampler,
|
||||
embeds,
|
||||
weight,
|
||||
start_at,
|
||||
end_at,
|
||||
)
|
||||
|
||||
return (work_model, image)
|
||||
@@ -0,0 +1,87 @@
|
||||
import numbers
|
||||
from typing import Dict, Optional, Tuple
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange
|
||||
|
||||
class RMSNorm(nn.Module):
|
||||
def __init__(self, dim, eps: float, elementwise_affine: bool = True):
|
||||
super().__init__()
|
||||
|
||||
self.eps = eps
|
||||
|
||||
if isinstance(dim, numbers.Integral):
|
||||
dim = (dim,)
|
||||
|
||||
self.dim = torch.Size(dim)
|
||||
|
||||
if elementwise_affine:
|
||||
self.weight = nn.Parameter(torch.ones(dim))
|
||||
else:
|
||||
self.weight = None
|
||||
|
||||
def forward(self, hidden_states):
|
||||
input_dtype = hidden_states.dtype
|
||||
variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True)
|
||||
hidden_states = hidden_states * torch.rsqrt(variance + self.eps)
|
||||
|
||||
if self.weight is not None:
|
||||
# convert into half-precision if necessary
|
||||
if self.weight.dtype in [torch.float16, torch.bfloat16]:
|
||||
hidden_states = hidden_states.to(self.weight.dtype)
|
||||
hidden_states = hidden_states * self.weight
|
||||
else:
|
||||
hidden_states = hidden_states.to(input_dtype)
|
||||
|
||||
return hidden_states
|
||||
|
||||
class IPAFluxAttnProcessor2_0(nn.Module):
|
||||
"""Attention processor used typically in processing the SD3-like self-attention projections."""
|
||||
|
||||
def __init__(self, hidden_size, cross_attention_dim=None, scale=1.0, num_tokens=4, timestep_range=None):
|
||||
super().__init__()
|
||||
|
||||
self.hidden_size = hidden_size # 3072
|
||||
self.cross_attention_dim = cross_attention_dim # 4096
|
||||
self.scale = scale
|
||||
self.num_tokens = num_tokens
|
||||
|
||||
self.to_k_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False)
|
||||
self.to_v_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False)
|
||||
|
||||
self.norm_added_k = RMSNorm(128, eps=1e-5, elementwise_affine=False)
|
||||
self.norm_added_v = RMSNorm(128, eps=1e-5, elementwise_affine=False)
|
||||
self.timestep_range = timestep_range
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
num_heads,
|
||||
query,
|
||||
image_emb: torch.FloatTensor,
|
||||
t: torch.FloatTensor
|
||||
) -> torch.FloatTensor:
|
||||
# only apply IPA if timestep is within range
|
||||
if self.timestep_range is not None:
|
||||
if t[0] > self.timestep_range[0] or t[0] < self.timestep_range[1]:
|
||||
return None
|
||||
# `ip-adapter` projections
|
||||
ip_hidden_states = image_emb
|
||||
ip_hidden_states_key_proj = self.to_k_ip(ip_hidden_states)
|
||||
ip_hidden_states_value_proj = self.to_v_ip(ip_hidden_states)
|
||||
|
||||
ip_hidden_states_key_proj = rearrange(ip_hidden_states_key_proj, 'B L (H D) -> B H L D', H=num_heads)
|
||||
ip_hidden_states_value_proj = rearrange(ip_hidden_states_value_proj, 'B L (H D) -> B H L D', H=num_heads)
|
||||
|
||||
ip_hidden_states_key_proj = self.norm_added_k(ip_hidden_states_key_proj)
|
||||
ip_hidden_states_value_proj = self.norm_added_v(ip_hidden_states_value_proj)
|
||||
|
||||
ip_hidden_states = F.scaled_dot_product_attention(query.to(image_emb.device).to(image_emb.dtype),
|
||||
ip_hidden_states_key_proj,
|
||||
ip_hidden_states_value_proj,
|
||||
dropout_p=0.0, is_causal=False)
|
||||
|
||||
ip_hidden_states = rearrange(ip_hidden_states, "B H L D -> B L (H D)", H=num_heads)
|
||||
ip_hidden_states = ip_hidden_states.to(query.dtype).to(query.device)
|
||||
|
||||
return self.scale * ip_hidden_states
|
||||
@@ -0,0 +1,132 @@
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
|
||||
from .math import attention
|
||||
from comfy.ldm.flux.layers import DoubleStreamBlock, SingleStreamBlock
|
||||
from comfy import model_management as mm
|
||||
|
||||
class DoubleStreamBlockIPA(nn.Module):
|
||||
def __init__(self, original_block: DoubleStreamBlock, ip_adapter, image_emb):
|
||||
super().__init__()
|
||||
|
||||
mlp_hidden_dim = original_block.img_mlp[0].out_features
|
||||
mlp_ratio = mlp_hidden_dim / original_block.hidden_size
|
||||
mlp_hidden_dim = int(original_block.hidden_size * mlp_ratio)
|
||||
self.num_heads = original_block.num_heads
|
||||
self.hidden_size = original_block.hidden_size
|
||||
self.img_mod = original_block.img_mod
|
||||
self.img_norm1 = original_block.img_norm1
|
||||
self.img_attn = original_block.img_attn
|
||||
|
||||
self.img_norm2 = original_block.img_norm2
|
||||
self.img_mlp = original_block.img_mlp
|
||||
|
||||
self.txt_mod = original_block.txt_mod
|
||||
self.txt_norm1 = original_block.txt_norm1
|
||||
self.txt_attn = original_block.txt_attn
|
||||
|
||||
self.txt_norm2 = original_block.txt_norm2
|
||||
self.txt_mlp = original_block.txt_mlp
|
||||
|
||||
self.ip_adapter = ip_adapter
|
||||
self.image_emb = image_emb
|
||||
self.device = mm.get_torch_device()
|
||||
|
||||
def forward(self, img: Tensor, txt: Tensor, vec: Tensor, pe: Tensor, t: Tensor):
|
||||
img_mod1, img_mod2 = self.img_mod(vec)
|
||||
txt_mod1, txt_mod2 = self.txt_mod(vec)
|
||||
|
||||
# prepare image for attention
|
||||
img_modulated = self.img_norm1(img)
|
||||
img_modulated = (1 + img_mod1.scale) * img_modulated + img_mod1.shift
|
||||
img_qkv = self.img_attn.qkv(img_modulated)
|
||||
img_q, img_k, img_v = img_qkv.view(img_qkv.shape[0], img_qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3,
|
||||
1, 4)
|
||||
img_q, img_k = self.img_attn.norm(img_q, img_k, img_v)
|
||||
|
||||
# prepare txt for attention
|
||||
txt_modulated = self.txt_norm1(txt)
|
||||
txt_modulated = (1 + txt_mod1.scale) * txt_modulated + txt_mod1.shift
|
||||
txt_qkv = self.txt_attn.qkv(txt_modulated)
|
||||
txt_q, txt_k, txt_v = txt_qkv.view(txt_qkv.shape[0], txt_qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3,
|
||||
1, 4)
|
||||
txt_q, txt_k = self.txt_attn.norm(txt_q, txt_k, txt_v)
|
||||
|
||||
# run actual attention
|
||||
attn = attention(torch.cat((txt_q, img_q), dim=2),
|
||||
torch.cat((txt_k, img_k), dim=2),
|
||||
torch.cat((txt_v, img_v), dim=2), pe=pe)
|
||||
|
||||
txt_attn, img_attn = attn[:, : txt.shape[1]], attn[:, txt.shape[1]:]
|
||||
|
||||
ip_hidden_states = self.ip_adapter(self.num_heads, img_q, self.image_emb, t)
|
||||
if ip_hidden_states is not None:
|
||||
ip_hidden_states.to(device=self.device)
|
||||
img_attn = img_attn + ip_hidden_states
|
||||
|
||||
|
||||
# calculate the img bloks
|
||||
img = img + img_mod1.gate * self.img_attn.proj(img_attn)
|
||||
img = img + img_mod2.gate * self.img_mlp((1 + img_mod2.scale) * self.img_norm2(img) + img_mod2.shift)
|
||||
|
||||
# calculate the txt bloks
|
||||
txt += txt_mod1.gate * self.txt_attn.proj(txt_attn)
|
||||
txt += txt_mod2.gate * self.txt_mlp((1 + txt_mod2.scale) * self.txt_norm2(txt) + txt_mod2.shift)
|
||||
|
||||
if txt.dtype == torch.float16:
|
||||
txt = torch.nan_to_num(txt, nan=0.0, posinf=65504, neginf=-65504)
|
||||
|
||||
return img, txt
|
||||
|
||||
|
||||
class SingleStreamBlockIPA(nn.Module):
|
||||
"""
|
||||
A DiT block with parallel linear layers as described in
|
||||
https://arxiv.org/abs/2302.05442 and adapted modulation interface.
|
||||
"""
|
||||
|
||||
def __init__(self, original_block: SingleStreamBlock, ip_adapter, image_emb):
|
||||
super().__init__()
|
||||
self.hidden_dim = original_block.hidden_size
|
||||
self.num_heads = original_block.num_heads
|
||||
self.scale = original_block.scale
|
||||
|
||||
self.mlp_hidden_dim = original_block.mlp_hidden_dim
|
||||
# qkv and mlp_in
|
||||
self.linear1 = original_block.linear1
|
||||
# proj and mlp_out
|
||||
self.linear2 = original_block.linear2
|
||||
|
||||
self.norm = original_block.norm
|
||||
|
||||
self.hidden_size = original_block.hidden_size
|
||||
self.pre_norm = original_block.pre_norm
|
||||
|
||||
self.mlp_act = original_block.mlp_act
|
||||
self.modulation = original_block.modulation
|
||||
|
||||
self.ip_adapter = ip_adapter
|
||||
self.image_emb = image_emb
|
||||
self.device = mm.get_torch_device()
|
||||
|
||||
def forward(self, x: Tensor, vec: Tensor, pe: Tensor, t: Tensor) -> Tensor:
|
||||
mod, _ = self.modulation(vec)
|
||||
x_mod = (1 + mod.scale) * self.pre_norm(x) + mod.shift
|
||||
qkv, mlp = torch.split(self.linear1(x_mod), [3 * self.hidden_size, self.mlp_hidden_dim], dim=-1)
|
||||
|
||||
q, k, v = qkv.view(qkv.shape[0], qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
|
||||
q, k = self.norm(q, k, v)
|
||||
|
||||
# compute attention
|
||||
attn = attention(q, k, v, pe=pe)
|
||||
|
||||
ip_hidden_states = self.ip_adapter(self.num_heads, q, self.image_emb, t)
|
||||
if ip_hidden_states is not None:
|
||||
ip_hidden_states.to(device=self.device)
|
||||
attn = attn + ip_hidden_states
|
||||
# compute activation in mlp stream, cat again and run second linear layer
|
||||
output = self.linear2(torch.cat((attn, self.mlp_act(mlp)), 2))
|
||||
x += mod.gate * output
|
||||
if x.dtype == torch.float16:
|
||||
x = torch.nan_to_num(x, nan=0.0, posinf=65504, neginf=-65504)
|
||||
return x
|
||||
@@ -0,0 +1,35 @@
|
||||
import torch
|
||||
from einops import rearrange
|
||||
from torch import Tensor
|
||||
from comfy.ldm.modules.attention import optimized_attention
|
||||
import comfy.model_management
|
||||
|
||||
def attention(q: Tensor, k: Tensor, v: Tensor, pe: Tensor) -> Tensor:
|
||||
q, k = apply_rope(q, k, pe)
|
||||
|
||||
heads = q.shape[1]
|
||||
x = optimized_attention(q, k, v, heads, skip_reshape=True)
|
||||
return x
|
||||
|
||||
|
||||
def rope(pos: Tensor, dim: int, theta: int) -> Tensor:
|
||||
assert dim % 2 == 0
|
||||
if comfy.model_management.is_device_mps(pos.device) or comfy.model_management.is_intel_xpu():
|
||||
device = torch.device("cpu")
|
||||
else:
|
||||
device = pos.device
|
||||
|
||||
scale = torch.linspace(0, (dim - 2) / dim, steps=dim//2, dtype=torch.float64, device=device)
|
||||
omega = 1.0 / (theta**scale)
|
||||
out = torch.einsum("...n,d->...nd", pos.to(dtype=torch.float32, device=device), omega)
|
||||
out = torch.stack([torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1)
|
||||
out = rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2)
|
||||
return out.to(dtype=torch.float32, device=pos.device)
|
||||
|
||||
|
||||
def apply_rope(xq: Tensor, xk: Tensor, freqs_cis: Tensor):
|
||||
xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2)
|
||||
xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2)
|
||||
xq_out = freqs_cis[..., 0] * xq_[..., 0] + freqs_cis[..., 1] * xq_[..., 1]
|
||||
xk_out = freqs_cis[..., 0] * xk_[..., 0] + freqs_cis[..., 1] * xk_[..., 1]
|
||||
return xq_out.reshape(*xq.shape).type_as(xq), xk_out.reshape(*xk.shape).type_as(xk)
|
||||
@@ -0,0 +1,219 @@
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
from einops import rearrange
|
||||
|
||||
from comfy.ldm.modules.attention import optimized_attention
|
||||
from comfy.ldm.modules.diffusionmodules.mmdit import (RMSNorm, JointBlock,)
|
||||
|
||||
|
||||
class AdaLayerNorm(nn.Module):
|
||||
"""
|
||||
Norm layer adaptive layer norm zero (adaLN-Zero).
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the embeddings dictionary.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, time_embedding_dim=None, mode="normal"):
|
||||
super().__init__()
|
||||
|
||||
self.silu = nn.SiLU()
|
||||
num_params_dict = dict(
|
||||
zero=6,
|
||||
normal=2,
|
||||
)
|
||||
num_params = num_params_dict[mode]
|
||||
self.linear = nn.Linear(
|
||||
time_embedding_dim or embedding_dim, num_params * embedding_dim, bias=True
|
||||
)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False, eps=1e-6)
|
||||
self.mode = mode
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x,
|
||||
hidden_dtype=None,
|
||||
emb=None,
|
||||
):
|
||||
emb = self.linear(self.silu(emb))
|
||||
if self.mode == "normal":
|
||||
shift_msa, scale_msa = emb.chunk(2, dim=1)
|
||||
x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
|
||||
return x
|
||||
|
||||
elif self.mode == "zero":
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = emb.chunk(
|
||||
6, dim=1
|
||||
)
|
||||
x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
|
||||
return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
|
||||
|
||||
|
||||
class IPAttnProcessor(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size=None,
|
||||
cross_attention_dim=None,
|
||||
ip_hidden_states_dim=None,
|
||||
ip_encoder_hidden_states_dim=None,
|
||||
head_dim=None,
|
||||
timesteps_emb_dim=1280,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.norm_ip = AdaLayerNorm(
|
||||
ip_hidden_states_dim, time_embedding_dim=timesteps_emb_dim
|
||||
)
|
||||
self.to_k_ip = nn.Linear(ip_hidden_states_dim, hidden_size, bias=False)
|
||||
self.to_v_ip = nn.Linear(ip_hidden_states_dim, hidden_size, bias=False)
|
||||
self.norm_q = RMSNorm(head_dim, 1e-6)
|
||||
self.norm_k = RMSNorm(head_dim, 1e-6)
|
||||
self.norm_ip_k = RMSNorm(head_dim, 1e-6)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
ip_hidden_states,
|
||||
img_query,
|
||||
img_key=None,
|
||||
img_value=None,
|
||||
t_emb=None,
|
||||
n_heads=1,
|
||||
):
|
||||
if ip_hidden_states is None:
|
||||
return None
|
||||
|
||||
if not hasattr(self, "to_k_ip") or not hasattr(self, "to_v_ip"):
|
||||
return None
|
||||
|
||||
# norm ip input
|
||||
norm_ip_hidden_states = self.norm_ip(ip_hidden_states, emb=t_emb)
|
||||
|
||||
# to k and v
|
||||
ip_key = self.to_k_ip(norm_ip_hidden_states)
|
||||
ip_value = self.to_v_ip(norm_ip_hidden_states)
|
||||
|
||||
# reshape
|
||||
img_query = rearrange(img_query, "b l (h d) -> b h l d", h=n_heads)
|
||||
img_key = rearrange(img_key, "b l (h d) -> b h l d", h=n_heads)
|
||||
# note that the image is in a different shape: b l h d
|
||||
# so we transpose to b h l d
|
||||
# or do we have to transpose here?
|
||||
img_value = torch.transpose(img_value, 1, 2)
|
||||
ip_key = rearrange(ip_key, "b l (h d) -> b h l d", h=n_heads)
|
||||
ip_value = rearrange(ip_value, "b l (h d) -> b h l d", h=n_heads)
|
||||
|
||||
# norm
|
||||
img_query = self.norm_q(img_query)
|
||||
img_key = self.norm_k(img_key)
|
||||
ip_key = self.norm_ip_k(ip_key)
|
||||
|
||||
# cat img
|
||||
key = torch.cat([img_key, ip_key], dim=2)
|
||||
value = torch.cat([img_value, ip_value], dim=2)
|
||||
|
||||
#
|
||||
ip_hidden_states = F.scaled_dot_product_attention(
|
||||
img_query, key, value, dropout_p=0.0, is_causal=False
|
||||
)
|
||||
ip_hidden_states = rearrange(ip_hidden_states, "b h l d -> b l (h d)")
|
||||
ip_hidden_states = ip_hidden_states.to(img_query.dtype)
|
||||
return ip_hidden_states
|
||||
|
||||
|
||||
class JointBlockIPWrapper:
|
||||
"""To be used as a patch_replace with Comfy"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
original_block: JointBlock,
|
||||
adapter: IPAttnProcessor,
|
||||
ip_options=None,
|
||||
):
|
||||
self.original_block = original_block
|
||||
self.adapter = adapter
|
||||
if ip_options is None:
|
||||
ip_options = {}
|
||||
self.ip_options = ip_options
|
||||
|
||||
def block_mixing(self, context, x, context_block, x_block, c):
|
||||
"""
|
||||
Comes from mmdit.py. Modified to add ipadapter attention.
|
||||
"""
|
||||
context_qkv, context_intermediates = context_block.pre_attention(context, c)
|
||||
|
||||
if x_block.x_block_self_attn:
|
||||
x_qkv, x_qkv2, x_intermediates = x_block.pre_attention_x(x, c)
|
||||
else:
|
||||
x_qkv, x_intermediates = x_block.pre_attention(x, c)
|
||||
|
||||
qkv = tuple(torch.cat((context_qkv[j], x_qkv[j]), dim=1) for j in range(3))
|
||||
|
||||
attn = optimized_attention(
|
||||
qkv[0],
|
||||
qkv[1],
|
||||
qkv[2],
|
||||
heads=x_block.attn.num_heads,
|
||||
)
|
||||
context_attn, x_attn = (
|
||||
attn[:, : context_qkv[0].shape[1]],
|
||||
attn[:, context_qkv[0].shape[1] :],
|
||||
)
|
||||
# if the current timestep is not in the ipadapter enabling range, then the resampler wasn't run
|
||||
# and the hidden states will be None
|
||||
if (
|
||||
self.ip_options["hidden_states"] is not None
|
||||
and self.ip_options["t_emb"] is not None
|
||||
):
|
||||
# IP-Adapter
|
||||
ip_attn = self.adapter(
|
||||
self.ip_options["hidden_states"],
|
||||
*x_qkv,
|
||||
self.ip_options["t_emb"],
|
||||
x_block.attn.num_heads,
|
||||
)
|
||||
x_attn = x_attn + ip_attn * self.ip_options["weight"]
|
||||
|
||||
# Everything else is unchanged
|
||||
if not context_block.pre_only:
|
||||
context = context_block.post_attention(context_attn, *context_intermediates)
|
||||
|
||||
else:
|
||||
context = None
|
||||
if x_block.x_block_self_attn:
|
||||
attn2 = optimized_attention(
|
||||
x_qkv2[0],
|
||||
x_qkv2[1],
|
||||
x_qkv2[2],
|
||||
heads=x_block.attn2.num_heads,
|
||||
)
|
||||
x = x_block.post_attention_x(x_attn, attn2, *x_intermediates)
|
||||
else:
|
||||
x = x_block.post_attention(x_attn, *x_intermediates)
|
||||
return context, x
|
||||
|
||||
def __call__(self, args, _):
|
||||
# Code from mmdit.py:
|
||||
# in this case, we're blocks_replace[("double_block", i)]
|
||||
# note that although we're passed the original block,
|
||||
# we can't actually get it from inside its wrapper
|
||||
# (which would simplify the whole code...)
|
||||
# ```
|
||||
# def block_wrap(args):
|
||||
# out = {}
|
||||
# out["txt"], out["img"] = self.joint_blocks[i](args["txt"], args["img"], c=args["vec"])
|
||||
# return out
|
||||
# out = blocks_replace[("double_block", i)]({"img": x, "txt": context, "vec": c_mod}, {"original_block": block_wrap})
|
||||
# context = out["txt"]
|
||||
# x = out["img"]
|
||||
# ```
|
||||
c, x = self.block_mixing(
|
||||
args["txt"],
|
||||
args["img"],
|
||||
self.original_block.context_block,
|
||||
self.original_block.x_block,
|
||||
c=args["vec"],
|
||||
)
|
||||
return {"txt": c, "img": x}
|
||||
@@ -0,0 +1,385 @@
|
||||
# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from typing import Optional
|
||||
|
||||
|
||||
ACTIVATION_FUNCTIONS = {
|
||||
"swish": nn.SiLU(),
|
||||
"silu": nn.SiLU(),
|
||||
"mish": nn.Mish(),
|
||||
"gelu": nn.GELU(),
|
||||
"relu": nn.ReLU(),
|
||||
}
|
||||
def get_activation(act_fn: str) -> nn.Module:
|
||||
"""Helper function to get activation function from string.
|
||||
|
||||
Args:
|
||||
act_fn (str): Name of activation function.
|
||||
|
||||
Returns:
|
||||
nn.Module: Activation function.
|
||||
"""
|
||||
|
||||
act_fn = act_fn.lower()
|
||||
if act_fn in ACTIVATION_FUNCTIONS:
|
||||
return ACTIVATION_FUNCTIONS[act_fn]
|
||||
else:
|
||||
raise ValueError(f"Unsupported activation function: {act_fn}")
|
||||
|
||||
def get_timestep_embedding(
|
||||
timesteps: torch.Tensor,
|
||||
embedding_dim: int,
|
||||
flip_sin_to_cos: bool = False,
|
||||
downscale_freq_shift: float = 1,
|
||||
scale: float = 1,
|
||||
max_period: int = 10000,
|
||||
):
|
||||
"""
|
||||
This matches the implementation in Denoising Diffusion Probabilistic Models: Create sinusoidal timestep embeddings.
|
||||
|
||||
Args
|
||||
timesteps (torch.Tensor):
|
||||
a 1-D Tensor of N indices, one per batch element. These may be fractional.
|
||||
embedding_dim (int):
|
||||
the dimension of the output.
|
||||
flip_sin_to_cos (bool):
|
||||
Whether the embedding order should be `cos, sin` (if True) or `sin, cos` (if False)
|
||||
downscale_freq_shift (float):
|
||||
Controls the delta between frequencies between dimensions
|
||||
scale (float):
|
||||
Scaling factor applied to the embeddings.
|
||||
max_period (int):
|
||||
Controls the maximum frequency of the embeddings
|
||||
Returns
|
||||
torch.Tensor: an [N x dim] Tensor of positional embeddings.
|
||||
"""
|
||||
assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array"
|
||||
|
||||
half_dim = embedding_dim // 2
|
||||
exponent = -math.log(max_period) * torch.arange(
|
||||
start=0, end=half_dim, dtype=torch.float32, device=timesteps.device
|
||||
)
|
||||
exponent = exponent / (half_dim - downscale_freq_shift)
|
||||
|
||||
emb = torch.exp(exponent)
|
||||
emb = timesteps[:, None].float() * emb[None, :]
|
||||
|
||||
# scale embeddings
|
||||
emb = scale * emb
|
||||
|
||||
# concat sine and cosine embeddings
|
||||
emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)
|
||||
|
||||
# flip sine and cosine embeddings
|
||||
if flip_sin_to_cos:
|
||||
emb = torch.cat([emb[:, half_dim:], emb[:, :half_dim]], dim=-1)
|
||||
|
||||
# zero pad
|
||||
if embedding_dim % 2 == 1:
|
||||
emb = torch.nn.functional.pad(emb, (0, 1, 0, 0))
|
||||
return emb
|
||||
|
||||
class Timesteps(nn.Module):
|
||||
def __init__(self, num_channels: int, flip_sin_to_cos: bool, downscale_freq_shift: float, scale: int = 1):
|
||||
super().__init__()
|
||||
self.num_channels = num_channels
|
||||
self.flip_sin_to_cos = flip_sin_to_cos
|
||||
self.downscale_freq_shift = downscale_freq_shift
|
||||
self.scale = scale
|
||||
|
||||
def forward(self, timesteps):
|
||||
t_emb = get_timestep_embedding(
|
||||
timesteps,
|
||||
self.num_channels,
|
||||
flip_sin_to_cos=self.flip_sin_to_cos,
|
||||
downscale_freq_shift=self.downscale_freq_shift,
|
||||
scale=self.scale,
|
||||
)
|
||||
return t_emb
|
||||
|
||||
class TimestepEmbedding(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
time_embed_dim: int,
|
||||
act_fn: str = "silu",
|
||||
out_dim: int = None,
|
||||
post_act_fn: Optional[str] = None,
|
||||
cond_proj_dim=None,
|
||||
sample_proj_bias=True,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.linear_1 = nn.Linear(in_channels, time_embed_dim, sample_proj_bias)
|
||||
|
||||
if cond_proj_dim is not None:
|
||||
self.cond_proj = nn.Linear(cond_proj_dim, in_channels, bias=False)
|
||||
else:
|
||||
self.cond_proj = None
|
||||
|
||||
self.act = get_activation(act_fn)
|
||||
|
||||
if out_dim is not None:
|
||||
time_embed_dim_out = out_dim
|
||||
else:
|
||||
time_embed_dim_out = time_embed_dim
|
||||
self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim_out, sample_proj_bias)
|
||||
|
||||
if post_act_fn is None:
|
||||
self.post_act = None
|
||||
else:
|
||||
self.post_act = get_activation(post_act_fn)
|
||||
|
||||
def forward(self, sample, condition=None):
|
||||
if condition is not None:
|
||||
sample = sample + self.cond_proj(condition)
|
||||
sample = self.linear_1(sample)
|
||||
|
||||
if self.act is not None:
|
||||
sample = self.act(sample)
|
||||
|
||||
sample = self.linear_2(sample)
|
||||
|
||||
if self.post_act is not None:
|
||||
sample = self.post_act(sample)
|
||||
return sample
|
||||
|
||||
|
||||
# FFN
|
||||
def FeedForward(dim, mult=4):
|
||||
inner_dim = int(dim * mult)
|
||||
return nn.Sequential(
|
||||
nn.LayerNorm(dim),
|
||||
nn.Linear(dim, inner_dim, bias=False),
|
||||
nn.GELU(),
|
||||
nn.Linear(inner_dim, dim, bias=False),
|
||||
)
|
||||
|
||||
|
||||
def reshape_tensor(x, heads):
|
||||
bs, length, width = x.shape
|
||||
# (bs, length, width) --> (bs, length, n_heads, dim_per_head)
|
||||
x = x.view(bs, length, heads, -1)
|
||||
# (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head)
|
||||
x = x.transpose(1, 2)
|
||||
# (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head)
|
||||
x = x.reshape(bs, heads, length, -1)
|
||||
return x
|
||||
|
||||
|
||||
class PerceiverAttention(nn.Module):
|
||||
def __init__(self, *, dim, dim_head=64, heads=8):
|
||||
super().__init__()
|
||||
self.scale = dim_head**-0.5
|
||||
self.dim_head = dim_head
|
||||
self.heads = heads
|
||||
inner_dim = dim_head * heads
|
||||
|
||||
self.norm1 = nn.LayerNorm(dim)
|
||||
self.norm2 = nn.LayerNorm(dim)
|
||||
|
||||
self.to_q = nn.Linear(dim, inner_dim, bias=False)
|
||||
self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False)
|
||||
self.to_out = nn.Linear(inner_dim, dim, bias=False)
|
||||
|
||||
def forward(self, x, latents, shift=None, scale=None):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): image features
|
||||
shape (b, n1, D)
|
||||
latent (torch.Tensor): latent features
|
||||
shape (b, n2, D)
|
||||
"""
|
||||
x = self.norm1(x)
|
||||
latents = self.norm2(latents)
|
||||
|
||||
if shift is not None and scale is not None:
|
||||
latents = latents * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
|
||||
b, l, _ = latents.shape
|
||||
|
||||
q = self.to_q(latents)
|
||||
kv_input = torch.cat((x, latents), dim=-2)
|
||||
k, v = self.to_kv(kv_input).chunk(2, dim=-1)
|
||||
|
||||
q = reshape_tensor(q, self.heads)
|
||||
k = reshape_tensor(k, self.heads)
|
||||
v = reshape_tensor(v, self.heads)
|
||||
|
||||
# attention
|
||||
scale = 1 / math.sqrt(math.sqrt(self.dim_head))
|
||||
weight = (q * scale) @ (k * scale).transpose(
|
||||
-2, -1
|
||||
) # More stable with f16 than dividing afterwards
|
||||
weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype)
|
||||
out = weight @ v
|
||||
|
||||
out = out.permute(0, 2, 1, 3).reshape(b, l, -1)
|
||||
|
||||
return self.to_out(out)
|
||||
|
||||
|
||||
class Resampler(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim=1024,
|
||||
depth=8,
|
||||
dim_head=64,
|
||||
heads=16,
|
||||
num_queries=8,
|
||||
embedding_dim=768,
|
||||
output_dim=1024,
|
||||
ff_mult=4,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5)
|
||||
|
||||
self.proj_in = nn.Linear(embedding_dim, dim)
|
||||
|
||||
self.proj_out = nn.Linear(dim, output_dim)
|
||||
self.norm_out = nn.LayerNorm(output_dim)
|
||||
|
||||
self.layers = nn.ModuleList([])
|
||||
for _ in range(depth):
|
||||
self.layers.append(
|
||||
nn.ModuleList(
|
||||
[
|
||||
PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads),
|
||||
FeedForward(dim=dim, mult=ff_mult),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
latents = self.latents.repeat(x.size(0), 1, 1)
|
||||
|
||||
x = self.proj_in(x)
|
||||
|
||||
for attn, ff in self.layers:
|
||||
latents = attn(x, latents) + latents
|
||||
latents = ff(latents) + latents
|
||||
|
||||
latents = self.proj_out(latents)
|
||||
return self.norm_out(latents)
|
||||
|
||||
|
||||
class TimeResampler(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim=1024,
|
||||
depth=8,
|
||||
dim_head=64,
|
||||
heads=16,
|
||||
num_queries=8,
|
||||
embedding_dim=768,
|
||||
output_dim=1024,
|
||||
ff_mult=4,
|
||||
timestep_in_dim=320,
|
||||
timestep_flip_sin_to_cos=True,
|
||||
timestep_freq_shift=0,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5)
|
||||
|
||||
self.proj_in = nn.Linear(embedding_dim, dim)
|
||||
|
||||
self.proj_out = nn.Linear(dim, output_dim)
|
||||
self.norm_out = nn.LayerNorm(output_dim)
|
||||
|
||||
self.layers = nn.ModuleList([])
|
||||
for _ in range(depth):
|
||||
self.layers.append(
|
||||
nn.ModuleList(
|
||||
[
|
||||
# msa
|
||||
PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads),
|
||||
# ff
|
||||
FeedForward(dim=dim, mult=ff_mult),
|
||||
# adaLN
|
||||
nn.Sequential(nn.SiLU(), nn.Linear(dim, 4 * dim, bias=True)),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# time
|
||||
self.time_proj = Timesteps(
|
||||
timestep_in_dim, timestep_flip_sin_to_cos, timestep_freq_shift
|
||||
)
|
||||
self.time_embedding = TimestepEmbedding(timestep_in_dim, dim, act_fn="silu")
|
||||
|
||||
# adaLN
|
||||
# self.adaLN_modulation = nn.Sequential(
|
||||
# nn.SiLU(),
|
||||
# nn.Linear(timestep_out_dim, 6 * timestep_out_dim, bias=True)
|
||||
# )
|
||||
|
||||
def forward(self, x, timestep, need_temb=False):
|
||||
timestep_emb = self.embedding_time(x, timestep) # bs, dim
|
||||
|
||||
latents = self.latents.repeat(x.size(0), 1, 1)
|
||||
|
||||
x = self.proj_in(x)
|
||||
x = x + timestep_emb[:, None]
|
||||
|
||||
for attn, ff, adaLN_modulation in self.layers:
|
||||
shift_msa, scale_msa, shift_mlp, scale_mlp = adaLN_modulation(
|
||||
timestep_emb
|
||||
).chunk(4, dim=1)
|
||||
latents = attn(x, latents, shift_msa, scale_msa) + latents
|
||||
|
||||
res = latents
|
||||
for idx_ff in range(len(ff)):
|
||||
layer_ff = ff[idx_ff]
|
||||
latents = layer_ff(latents)
|
||||
if idx_ff == 0 and isinstance(layer_ff, nn.LayerNorm): # adaLN
|
||||
latents = latents * (
|
||||
1 + scale_mlp.unsqueeze(1)
|
||||
) + shift_mlp.unsqueeze(1)
|
||||
latents = latents + res
|
||||
|
||||
# latents = ff(latents) + latents
|
||||
|
||||
latents = self.proj_out(latents)
|
||||
latents = self.norm_out(latents)
|
||||
|
||||
if need_temb:
|
||||
return latents, timestep_emb
|
||||
else:
|
||||
return latents
|
||||
|
||||
def embedding_time(self, sample, timestep):
|
||||
|
||||
# 1. time
|
||||
timesteps = timestep
|
||||
if not torch.is_tensor(timesteps):
|
||||
# TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can
|
||||
# This would be a good case for the `match` statement (Python 3.10+)
|
||||
is_mps = sample.device.type == "mps"
|
||||
if isinstance(timestep, float):
|
||||
dtype = torch.float32 if is_mps else torch.float64
|
||||
else:
|
||||
dtype = torch.int32 if is_mps else torch.int64
|
||||
timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device)
|
||||
elif len(timesteps.shape) == 0:
|
||||
timesteps = timesteps[None].to(sample.device)
|
||||
|
||||
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
|
||||
timesteps = timesteps.expand(sample.shape[0])
|
||||
|
||||
t_emb = self.time_proj(timesteps)
|
||||
|
||||
# timesteps does not contain any weights and will always return f32 tensors
|
||||
# but time_embedding might actually be running in fp16. so we need to cast here.
|
||||
# there might be better ways to encapsulate this.
|
||||
t_emb = t_emb.to(dtype=sample.dtype)
|
||||
|
||||
emb = self.time_embedding(t_emb, None)
|
||||
return emb
|
||||
@@ -0,0 +1,120 @@
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from .flux.layers import DoubleStreamBlockIPA, SingleStreamBlockIPA
|
||||
from comfy.ldm.flux.layers import timestep_embedding
|
||||
from types import MethodType
|
||||
|
||||
def FluxUpdateModules(bi, ip_attn_procs, image_emb, is_patched):
|
||||
flux_model = bi.model
|
||||
bi.add_object_patch(f"diffusion_model.forward_orig", MethodType(forward_orig_ipa, flux_model.diffusion_model))
|
||||
dsb_count = len(flux_model.diffusion_model.double_blocks)
|
||||
ssb_count = len(flux_model.diffusion_model.single_blocks)
|
||||
for i in range(dsb_count):
|
||||
temp_layer = DoubleStreamBlockIPA(
|
||||
flux_model.diffusion_model.double_blocks[i], ip_attn_procs[f"double_blocks.{i}"], image_emb)
|
||||
bi.add_object_patch(f"diffusion_model.double_blocks.{i}",temp_layer)
|
||||
for i in range(ssb_count):
|
||||
temp_layer = SingleStreamBlockIPA(
|
||||
flux_model.diffusion_model.single_blocks[i], ip_attn_procs[f"single_blocks.{i}"], image_emb)
|
||||
bi.add_object_patch(f"diffusion_model.single_blocks.{i}", temp_layer)
|
||||
|
||||
def is_model_pathched(model):
|
||||
def test(mod):
|
||||
if isinstance(mod, DoubleStreamBlockIPA):
|
||||
return True
|
||||
else:
|
||||
for p in mod.children():
|
||||
if test(p):
|
||||
return True
|
||||
return False
|
||||
|
||||
result = test(model)
|
||||
return result
|
||||
|
||||
def forward_orig_ipa(
|
||||
self,
|
||||
img: Tensor,
|
||||
img_ids: Tensor,
|
||||
txt: Tensor,
|
||||
txt_ids: Tensor,
|
||||
timesteps: Tensor,
|
||||
y: Tensor,
|
||||
guidance: Tensor = None,
|
||||
control=None,
|
||||
transformer_options={},
|
||||
) -> Tensor:
|
||||
patches_replace = transformer_options.get("patches_replace", {})
|
||||
if img.ndim != 3 or txt.ndim != 3:
|
||||
raise ValueError("Input img and txt tensors must have 3 dimensions.")
|
||||
|
||||
# running on sequences img
|
||||
img = self.img_in(img)
|
||||
vec = self.time_in(timestep_embedding(timesteps, 256).to(img.dtype))
|
||||
if self.params.guidance_embed:
|
||||
if guidance is None:
|
||||
raise ValueError("Didn't get guidance strength for guidance distilled model.")
|
||||
vec = vec + self.guidance_in(timestep_embedding(guidance, 256).to(img.dtype))
|
||||
|
||||
vec = vec + self.vector_in(y[:,:self.params.vec_in_dim])
|
||||
txt = self.txt_in(txt)
|
||||
|
||||
ids = torch.cat((txt_ids, img_ids), dim=1)
|
||||
pe = self.pe_embedder(ids)
|
||||
|
||||
blocks_replace = patches_replace.get("dit", {})
|
||||
for i, block in enumerate(self.double_blocks):
|
||||
if ("double_block", i) in blocks_replace:
|
||||
def block_wrap(args):
|
||||
out = {}
|
||||
if isinstance(block, DoubleStreamBlockIPA): # ipadaper
|
||||
out["img"], out["txt"] = block(img=args["img"], txt=args["txt"], vec=args["vec"], pe=args["pe"], t=args["timesteps"])
|
||||
else:
|
||||
out["img"], out["txt"] = block(img=args["img"], txt=args["txt"], vec=args["vec"], pe=args["pe"])
|
||||
return out
|
||||
out = blocks_replace[("double_block", i)]({"img": img, "txt": txt, "vec": vec, "pe": pe, "timesteps": timesteps}, {"original_block": block_wrap})
|
||||
txt = out["txt"]
|
||||
img = out["img"]
|
||||
else:
|
||||
if isinstance(block, DoubleStreamBlockIPA): # ipadaper
|
||||
img, txt = block(img=img, txt=txt, vec=vec, pe=pe, t=timesteps)
|
||||
else:
|
||||
img, txt = block(img=img, txt=txt, vec=vec, pe=pe)
|
||||
|
||||
if control is not None: # Controlnet
|
||||
control_i = control.get("input")
|
||||
if i < len(control_i):
|
||||
add = control_i[i]
|
||||
if add is not None:
|
||||
img += add
|
||||
|
||||
img = torch.cat((txt, img), 1)
|
||||
|
||||
for i, block in enumerate(self.single_blocks):
|
||||
if ("single_block", i) in blocks_replace:
|
||||
def block_wrap(args):
|
||||
out = {}
|
||||
if isinstance(block, SingleStreamBlockIPA): # ipadaper
|
||||
out["img"] = block(args["img"], vec=args["vec"], pe=args["pe"], t=args["timesteps"])
|
||||
else:
|
||||
out["img"] = block(args["img"], vec=args["vec"], pe=args["pe"])
|
||||
return out
|
||||
|
||||
out = blocks_replace[("single_block", i)]({"img": img, "vec": vec, "pe": pe, "timesteps": timesteps}, {"original_block": block_wrap})
|
||||
img = out["img"]
|
||||
else:
|
||||
if isinstance(block, SingleStreamBlockIPA): # ipadaper
|
||||
img = block(img, vec=vec, pe=pe, t=timesteps)
|
||||
else:
|
||||
img = block(img, vec=vec, pe=pe)
|
||||
|
||||
if control is not None: # Controlnet
|
||||
control_o = control.get("output")
|
||||
if i < len(control_o):
|
||||
add = control_o[i]
|
||||
if add is not None:
|
||||
img[:, txt.shape[1] :, ...] += add
|
||||
|
||||
img = img[:, txt.shape[1] :, ...]
|
||||
|
||||
img = self.final_layer(img, vec) # (N, T, patch_size ** 2 * out_channels)
|
||||
return img
|
||||
@@ -242,6 +242,7 @@ class ChatGLMTokenizer(PreTrainedTokenizer):
|
||||
padding_strategy: PaddingStrategy = PaddingStrategy.DO_NOT_PAD,
|
||||
pad_to_multiple_of: Optional[int] = None,
|
||||
return_attention_mask: Optional[bool] = None,
|
||||
**kwargs
|
||||
) -> dict:
|
||||
"""
|
||||
Pad encoded inputs (on left/right and up to predefined length or max length in the batch)
|
||||
|
||||
+4
-3
@@ -1,6 +1,8 @@
|
||||
import json
|
||||
import os
|
||||
import torch
|
||||
import subprocess
|
||||
import sys
|
||||
import comfy.supported_models
|
||||
import comfy.model_patcher
|
||||
import comfy.model_management
|
||||
@@ -294,9 +296,8 @@ class applyKolorsUnet:
|
||||
|
||||
|
||||
def is_kolors_model(model):
|
||||
base: BaseModel = model.model
|
||||
model_config: comfy.supported_models.supported_models_base.BASE = base.model_config
|
||||
if isinstance(model_config, Kolors):
|
||||
unet_config = model.model.model_config.unet_config
|
||||
if unet_config and "adm_in_channels" in unet_config and unet_config["adm_in_channels"] == 5632:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -2,6 +2,7 @@
|
||||
#from https://github.com/huchenlei/ComfyUI-layerdiffuse
|
||||
import torch
|
||||
import comfy.model_management
|
||||
import comfy.lora
|
||||
import copy
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
@@ -59,7 +60,10 @@ class LayerDiffuse:
|
||||
image = image.movedim(-1, 1)
|
||||
|
||||
try:
|
||||
ModelPatcher.calculate_weight = calculate_weight_adjust_channel(ModelPatcher.calculate_weight)
|
||||
if hasattr(comfy.lora, "calculate_weight"):
|
||||
comfy.lora.calculate_weight = calculate_weight_adjust_channel(comfy.lora.calculate_weight)
|
||||
else:
|
||||
ModelPatcher.calculate_weight = calculate_weight_adjust_channel(ModelPatcher.calculate_weight)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import einops
|
||||
from comfy import model_management, utils
|
||||
from comfy.ldm.modules.attention import optimized_attention
|
||||
|
||||
|
||||
module_mapping_sd15 = {
|
||||
0: "input_blocks.1.1.transformer_blocks.0.attn1",
|
||||
1: "input_blocks.1.1.transformer_blocks.0.attn2",
|
||||
|
||||
@@ -324,9 +324,9 @@ try:
|
||||
"""Patches ComfyUI's LoRA weight application to accept multi-channel inputs."""
|
||||
@functools.wraps(func)
|
||||
def calculate_weight(
|
||||
self: ModelPatcher, patches, weight: torch.Tensor, key: str
|
||||
patches, weight: torch.Tensor, key: str, intermediate_type=torch.float32
|
||||
) -> torch.Tensor:
|
||||
weight = func(self, patches, weight, key)
|
||||
weight = func(patches, weight, key, intermediate_type)
|
||||
|
||||
for p in patches:
|
||||
alpha = p[0]
|
||||
|
||||
@@ -325,7 +325,7 @@ def advanced_encode(clip, text, token_normalization, weight_interpretation, w_ma
|
||||
out = None
|
||||
|
||||
if len(tokenized['l']) > 0 or len(tokenized['g']) > 0:
|
||||
if 'l' in tokenized:
|
||||
if clip.cond_stage_model.clip_l is not None:
|
||||
lg_out, l_pooled = advanced_encode_from_tokens(tokenized['l'],
|
||||
token_normalization,
|
||||
weight_interpretation,
|
||||
@@ -334,7 +334,7 @@ def advanced_encode(clip, text, token_normalization, weight_interpretation, w_ma
|
||||
else:
|
||||
l_pooled = torch.zeros((1, 768), device=model_management.intermediate_device())
|
||||
|
||||
if 'g' in tokenized:
|
||||
if clip.cond_stage_model.clip_g is not None:
|
||||
g_out, g_pooled = advanced_encode_from_tokens(tokenized['g'],
|
||||
token_normalization,
|
||||
weight_interpretation,
|
||||
@@ -354,7 +354,7 @@ def advanced_encode(clip, text, token_normalization, weight_interpretation, w_ma
|
||||
pooled = torch.cat((l_pooled, g_pooled), dim=-1)
|
||||
|
||||
# t5xxl
|
||||
if 't5xxl' in tokenized and clip.cond_stage_model.t5xxl is not None:
|
||||
if 't5xxl' in tokenized:
|
||||
t5_out, t5_pooled = advanced_encode_from_tokens(tokenized['t5xxl'],
|
||||
token_normalization,
|
||||
weight_interpretation,
|
||||
|
||||
+15
-11
@@ -8,32 +8,36 @@ from nodes import ConditioningConcat, ConditioningCombine, ConditioningAverage,
|
||||
|
||||
def prompt_to_cond(type, model, clip, clip_skip, lora_stack, text, prompt_token_normalization, prompt_weight_interpretation, a1111_prompt_style ,my_unique_id, prompt, easyCache, can_load_lora=True, steps=None, model_type=None):
|
||||
styles_selector = is_linked_styles_selector(prompt, my_unique_id, type)
|
||||
title = "正面提示词" if type == 'positive' else "负面提示词"
|
||||
log_node_warn("正在进行" + title + "...")
|
||||
|
||||
if model_type in ['hydit', 'flux']:
|
||||
embeddings_final, = CLIPTextEncode().encode(clip, text)
|
||||
return (embeddings_final, "", model, clip)
|
||||
title = "Positive encoding" if type == 'positive' else "Negative encoding"
|
||||
|
||||
# Translate cn to en
|
||||
if has_chinese(text):
|
||||
if model_type not in ['hydit'] and text is not None and has_chinese(text):
|
||||
text = zh_to_en([text])[0]
|
||||
|
||||
if model_type in ['hydit', 'flux', 'mochi']:
|
||||
log_node_warn(title + "...")
|
||||
embeddings_final, = CLIPTextEncode().encode(clip, text) if text is not None else (None,)
|
||||
|
||||
return (embeddings_final, "", model, clip)
|
||||
|
||||
log_node_warn(title + "...")
|
||||
|
||||
positive_seed = find_wildcards_seed(my_unique_id, text, prompt)
|
||||
model, clip, text, cond_decode, show_prompt, pipe_lora_stack = process_with_loras(
|
||||
text, model, clip, type, positive_seed, can_load_lora, lora_stack, easyCache)
|
||||
wildcard_prompt = cond_decode if show_prompt or styles_selector else ""
|
||||
|
||||
clipped = clip.clone()
|
||||
if clip_skip != 0:
|
||||
clipped.clip_layer(clip_skip)
|
||||
# 当clip模型不存在t5xxl时,可执行跳过层
|
||||
if not hasattr(clip.cond_stage_model, 't5xxl'):
|
||||
if clip_skip != 0:
|
||||
clipped.clip_layer(clip_skip)
|
||||
|
||||
log_node_warn("正在进行" + title + "编码...")
|
||||
steps = steps if steps is not None else find_nearest_steps(my_unique_id, prompt)
|
||||
return (advanced_encode(clipped, text, prompt_token_normalization,
|
||||
prompt_weight_interpretation, w_max=1.0,
|
||||
apply_to_pooled='enable',
|
||||
a1111_prompt_style=a1111_prompt_style, steps=steps), wildcard_prompt, model, clipped)
|
||||
a1111_prompt_style=a1111_prompt_style, steps=steps) if text is not None else None, wildcard_prompt, model, clipped)
|
||||
|
||||
def set_cond(old_cond, new_cond, mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end):
|
||||
if not old_cond:
|
||||
|
||||
@@ -9,7 +9,7 @@ class easyControlnet:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def apply(self, control_net_name, image, positive, negative, strength, start_percent=0, end_percent=1, control_net=None, scale_soft_weights=1, mask=None, union_type=None, easyCache=None, use_cache=True, model=None):
|
||||
def apply(self, control_net_name, image, positive, negative, strength, start_percent=0, end_percent=1, control_net=None, scale_soft_weights=1, mask=None, union_type=None, easyCache=None, use_cache=True, model=None, vae=None):
|
||||
if strength == 0:
|
||||
return (positive, negative)
|
||||
|
||||
@@ -69,7 +69,7 @@ class easyControlnet:
|
||||
if prev_cnet in cnets:
|
||||
c_net = cnets[prev_cnet]
|
||||
else:
|
||||
c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent))
|
||||
c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent), vae)
|
||||
c_net.set_previous_controlnet(prev_cnet)
|
||||
cnets[prev_cnet] = c_net
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import json
|
||||
import os
|
||||
import yaml
|
||||
import requests
|
||||
import pathlib
|
||||
from aiohttp import web
|
||||
|
||||
root_path = pathlib.Path(__file__).parent.parent.parent
|
||||
config_path = os.path.join(root_path,'config.yaml')
|
||||
class FluxAIAPI:
|
||||
def __init__(self):
|
||||
self.api_url = "https://fluxaiimagegenerator.com/api"
|
||||
self.origin = "https://fluxaiimagegenerator.com"
|
||||
self.user_agent = None
|
||||
self.cookie = None
|
||||
|
||||
def promptGenerate(self, text, cookies=None):
|
||||
cookie = self.cookie if cookies is None else cookies
|
||||
if cookie is None:
|
||||
if os.path.isfile(config_path):
|
||||
with open(config_path, 'r') as f:
|
||||
data = yaml.load(f, Loader=yaml.FullLoader)
|
||||
if 'FLUXAI_COOKIE' not in data:
|
||||
raise Exception("Please add FLUXAI_COOKIE to config.yaml")
|
||||
if "FLUXAI_USER_AGENT" in data:
|
||||
self.user_agent = data["FLUXAI_USER_AGENT"]
|
||||
self.cookie = cookie = data['FLUXAI_COOKIE']
|
||||
|
||||
headers = {
|
||||
"Cookie": cookie,
|
||||
"Referer": "https://fluxaiimagegenerator.com/flux-prompt-generator",
|
||||
"Origin": self.origin,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if self.user_agent is not None:
|
||||
headers['User-Agent'] = self.user_agent
|
||||
|
||||
url = self.api_url + '/prompt'
|
||||
json = {
|
||||
"prompt": text
|
||||
}
|
||||
|
||||
response = requests.post(url, json=json, headers=headers)
|
||||
res = response.json()
|
||||
data = res['data']
|
||||
if "error" in data:
|
||||
return data['error']
|
||||
elif "prompt" in data:
|
||||
return data['prompt']
|
||||
|
||||
fluxaiAPI = FluxAIAPI()
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
#credit to Acly for this module
|
||||
#from https://github.com/Acly/comfyui-inpaint-nodes
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import comfy
|
||||
from comfy.model_base import BaseModel
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
from comfy.model_management import cast_to_device
|
||||
|
||||
from .log import log_node_warn, log_node_error, log_node_info
|
||||
|
||||
# Inpaint
|
||||
original_calculate_weight = ModelPatcher.calculate_weight
|
||||
injected_model_patcher_calculate_weight = False
|
||||
|
||||
class InpaintHead(torch.nn.Module):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.head = torch.nn.Parameter(torch.empty(size=(320, 5, 3, 3), device="cpu"))
|
||||
|
||||
def __call__(self, x):
|
||||
x = F.pad(x, (1, 1, 1, 1), "replicate")
|
||||
return F.conv2d(x, weight=self.head)
|
||||
|
||||
def calculate_weight_patched(self: ModelPatcher, patches, weight, key):
|
||||
remaining = []
|
||||
|
||||
for p in patches:
|
||||
alpha = p[0]
|
||||
v = p[1]
|
||||
|
||||
is_fooocus_patch = isinstance(v, tuple) and len(v) == 2 and v[0] == "fooocus"
|
||||
if not is_fooocus_patch:
|
||||
remaining.append(p)
|
||||
continue
|
||||
|
||||
if alpha != 0.0:
|
||||
v = v[1]
|
||||
w1 = cast_to_device(v[0], weight.device, torch.float32)
|
||||
if w1.shape == weight.shape:
|
||||
w_min = cast_to_device(v[1], weight.device, torch.float32)
|
||||
w_max = cast_to_device(v[2], weight.device, torch.float32)
|
||||
w1 = (w1 / 255.0) * (w_max - w_min) + w_min
|
||||
weight += alpha * cast_to_device(w1, weight.device, weight.dtype)
|
||||
else:
|
||||
pass
|
||||
# log_node_warn(self.node_name,
|
||||
# f"Shape mismatch {key}, weight not merged ({w1.shape} != {weight.shape})"
|
||||
# )
|
||||
|
||||
if len(remaining) > 0:
|
||||
return original_calculate_weight(self, remaining, weight, key)
|
||||
return weight
|
||||
|
||||
def inject_patched_calculate_weight():
|
||||
global injected_model_patcher_calculate_weight
|
||||
if not injected_model_patcher_calculate_weight:
|
||||
print(
|
||||
"[comfyui-inpaint-nodes] Injecting patched comfy.model_patcher.ModelPatcher.calculate_weight"
|
||||
)
|
||||
ModelPatcher.calculate_weight = calculate_weight_patched
|
||||
injected_model_patcher_calculate_weight = True
|
||||
|
||||
class InpaintWorker:
|
||||
def __init__(self, node_name):
|
||||
self.node_name = node_name if node_name is not None else ""
|
||||
|
||||
def load_fooocus_patch(self, lora: dict, to_load: dict):
|
||||
patch_dict = {}
|
||||
loaded_keys = set()
|
||||
for key in to_load.values():
|
||||
if value := lora.get(key, None):
|
||||
patch_dict[key] = ("fooocus", value)
|
||||
loaded_keys.add(key)
|
||||
|
||||
not_loaded = sum(1 for x in lora if x not in loaded_keys)
|
||||
if not_loaded > 0:
|
||||
log_node_info(self.node_name,
|
||||
f"{len(loaded_keys)} Lora keys loaded, {not_loaded} remaining keys not found in model."
|
||||
)
|
||||
return patch_dict
|
||||
|
||||
|
||||
def patch(self, model, latent, patch):
|
||||
base_model: BaseModel = model.model
|
||||
latent_pixels = base_model.process_latent_in(latent["samples"])
|
||||
noise_mask = latent["noise_mask"].round()
|
||||
latent_mask = F.max_pool2d(noise_mask, (8, 8)).round().to(latent_pixels)
|
||||
|
||||
inpaint_head_model, inpaint_lora = patch
|
||||
feed = torch.cat([latent_mask, latent_pixels], dim=1)
|
||||
inpaint_head_model.to(device=feed.device, dtype=feed.dtype)
|
||||
inpaint_head_feature = inpaint_head_model(feed)
|
||||
|
||||
def input_block_patch(h, transformer_options):
|
||||
if transformer_options["block"][1] == 0:
|
||||
h = h + inpaint_head_feature.to(h)
|
||||
return h
|
||||
|
||||
lora_keys = comfy.lora.model_lora_keys_unet(model.model, {})
|
||||
lora_keys.update({x: x for x in base_model.state_dict().keys()})
|
||||
loaded_lora = self.load_fooocus_patch(inpaint_lora, lora_keys)
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_input_block_patch(input_block_patch)
|
||||
patched = m.add_patches(loaded_lora, 1.0)
|
||||
|
||||
not_patched_count = sum(1 for x in loaded_lora if x not in patched)
|
||||
if not_patched_count > 0:
|
||||
log_node_error(self.node_name, f"Failed to patch {not_patched_count} keys")
|
||||
|
||||
inject_patched_calculate_weight()
|
||||
return (m,)
|
||||
+21
-14
@@ -1,4 +1,4 @@
|
||||
import time, os, psutil
|
||||
import re, time, os, psutil
|
||||
import folder_paths
|
||||
import comfy.utils
|
||||
import comfy.sd
|
||||
@@ -238,7 +238,11 @@ class easyLoader:
|
||||
config_path = folder_paths.get_full_path("configs", config_name)
|
||||
loaded_ckpt = comfy.sd.load_checkpoint(config_path, ckpt_path, output_vae=True, output_clip=output_clip, embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
||||
else:
|
||||
loaded_ckpt = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=output_clip, output_clipvision=output_clipvision, embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
||||
model_options = {}
|
||||
if re.search("nf4", ckpt_name):
|
||||
from ..bitsandbytes_NF4 import OPS
|
||||
model_options = {"custom_operations": OPS}
|
||||
loaded_ckpt = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=output_clip, output_clipvision=output_clipvision, embedding_directory=folder_paths.get_folder_paths("embeddings"), model_options=model_options)
|
||||
|
||||
self.add_to_cache("ckpt", cache_name, loaded_ckpt[0])
|
||||
self.add_to_cache("bvae", cache_name, loaded_ckpt[2])
|
||||
@@ -308,6 +312,8 @@ class easyLoader:
|
||||
clip_type = comfy.sd.CLIPType.STABLE_CASCADE
|
||||
elif type == 'sd3':
|
||||
clip_type = comfy.sd.CLIPType.SD3
|
||||
elif type == 'flux':
|
||||
clip_type = comfy.sd.CLIPType.FLUX
|
||||
elif type == 'stable_audio':
|
||||
clip_type = comfy.sd.CLIPType.STABLE_AUDIO
|
||||
clip_path = folder_paths.get_full_path("clip", clip_name)
|
||||
@@ -317,7 +323,7 @@ class easyLoader:
|
||||
|
||||
return load_clip
|
||||
|
||||
def load_lora(self, lora, model=None, clip=None, type=None):
|
||||
def load_lora(self, lora, model=None, clip=None, type=None , use_cache=True):
|
||||
lora_name = lora["lora_name"]
|
||||
model = model if model is not None else lora["model"]
|
||||
clip = clip if clip is not None else lora["clip"]
|
||||
@@ -332,7 +338,7 @@ class easyLoader:
|
||||
|
||||
unique_id = f'{model_hash};{clip_hash};{lora_name};{model_strength};{clip_strength}'
|
||||
|
||||
if unique_id in self.loaded_objects["lora"]:
|
||||
if use_cache and unique_id in self.loaded_objects["lora"]:
|
||||
log_node_info("Load LORA",f"{lora_name} cached")
|
||||
return self.loaded_objects["lora"][unique_id][0]
|
||||
|
||||
@@ -390,8 +396,9 @@ class easyLoader:
|
||||
else:
|
||||
model, clip = comfy.sd.load_lora_for_models(model, clip, _lora, model_strength, clip_strength)
|
||||
|
||||
self.add_to_cache("lora", unique_id, (model, clip))
|
||||
self.eviction_based_on_memory()
|
||||
if use_cache:
|
||||
self.add_to_cache("lora", unique_id, (model, clip))
|
||||
self.eviction_based_on_memory()
|
||||
else:
|
||||
log_node_error(f"LORA NOT FOUND", orig_lora_name)
|
||||
|
||||
@@ -418,7 +425,7 @@ class easyLoader:
|
||||
|
||||
return None
|
||||
|
||||
def load_main(self, ckpt_name, config_name, vae_name, lora_name, lora_model_strength, lora_clip_strength, optional_lora_stack, model_override, clip_override, vae_override, prompt):
|
||||
def load_main(self, ckpt_name, config_name, vae_name, lora_name, lora_model_strength, lora_clip_strength, optional_lora_stack, model_override, clip_override, vae_override, prompt, nf4=False):
|
||||
model: ModelPatcher | None = None
|
||||
clip: comfy.sd.CLIP | None = None
|
||||
vae: comfy.sd.VAE | None = None
|
||||
@@ -438,19 +445,19 @@ class easyLoader:
|
||||
ckpt_name_1 = node["inputs"]["ckpt_name_1"]
|
||||
model, clip, vae, clip_vision = self.load_checkpoint(ckpt_name_1)
|
||||
can_load_lora = False
|
||||
# Load models
|
||||
elif model_override is not None and clip_override is not None and vae_override is not None:
|
||||
model = model_override
|
||||
clip = clip_override
|
||||
vae = vae_override
|
||||
elif model_override is not None:
|
||||
raise Exception(f"[ERROR] clip or vae is missing")
|
||||
elif vae_override is not None:
|
||||
raise Exception(f"[ERROR] model or clip is missing")
|
||||
elif clip_override is not None:
|
||||
raise Exception(f"[ERROR] model or vae is missing")
|
||||
else:
|
||||
model, clip, vae, clip_vision = self.load_checkpoint(ckpt_name, config_name)
|
||||
if model_override is not None:
|
||||
model = model_override
|
||||
if vae_override is not None:
|
||||
vae = vae_override
|
||||
elif clip_override is not None:
|
||||
clip = clip_override
|
||||
|
||||
|
||||
if optional_lora_stack is not None and can_load_lora:
|
||||
for lora in optional_lora_stack:
|
||||
|
||||
+1052
-909
File diff suppressed because it is too large
Load Diff
@@ -80,7 +80,7 @@ def has_chinese(text):
|
||||
_text = text
|
||||
_text = re.sub(r'<.*?>', '', _text)
|
||||
_text = re.sub(r'__.*?__', '', _text)
|
||||
_text = re.sub(r'embedding:.*?(\d+)?', '', _text)
|
||||
_text = re.sub(r'embedding:.*?$', '', _text)
|
||||
for char in _text:
|
||||
if '\u4e00' <= char <= '\u9fff':
|
||||
has_cn = True
|
||||
@@ -95,7 +95,6 @@ def translate(text):
|
||||
if not os.path.exists(zh_en_model_path):
|
||||
zh_en_model_path = 'Helsinki-NLP/opus-mt-zh-en'
|
||||
|
||||
print(zh_en_model_path)
|
||||
if zh_en_model is None:
|
||||
|
||||
zh_en_model = AutoModelForSeq2SeqLM.from_pretrained(zh_en_model_path).eval()
|
||||
|
||||
+7
-4
@@ -121,6 +121,8 @@ def get_sd_version(model):
|
||||
return 'hydit'
|
||||
elif isinstance(model_config, comfy.supported_models.Flux):
|
||||
return 'flux'
|
||||
elif isinstance(model_config, comfy.supported_models.GenmoMochi):
|
||||
return 'mochi'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
@@ -182,8 +184,9 @@ def find_wildcards_seed(clip_id, text, prompt):
|
||||
else:
|
||||
return None
|
||||
|
||||
def is_linked_styles_selector(prompt, my_unique_id, prompt_type='positive'):
|
||||
inputs_values = prompt[my_unique_id]['inputs'][prompt_type] if prompt_type in prompt[my_unique_id][
|
||||
def is_linked_styles_selector(prompt, unique_id, prompt_type='positive'):
|
||||
unique_id = unique_id.split('.')[len(unique_id.split('.')) - 1] if "." in unique_id else unique_id
|
||||
inputs_values = prompt[unique_id]['inputs'][prompt_type] if prompt_type in prompt[unique_id][
|
||||
'inputs'] else None
|
||||
if type(inputs_values) == list and inputs_values != 'undefined' and inputs_values[0]:
|
||||
return True if prompt[inputs_values[0]] and prompt[inputs_values[0]]['class_type'] == 'easy stylesSelector' else False
|
||||
@@ -246,9 +249,9 @@ def to_lora_patch_dict(state_dict: dict) -> dict:
|
||||
def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=None):
|
||||
"""Save or Preview Image"""
|
||||
from nodes import PreviewImage, SaveImage
|
||||
if output_type == "Hide":
|
||||
if output_type in ["Hide", "None"]:
|
||||
return list()
|
||||
if output_type in ["Preview", "Preview&Choose"]:
|
||||
elif output_type in ["Preview", "Preview&Choose"]:
|
||||
filename_prefix = 'easyPreview'
|
||||
results = PreviewImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
return results['ui']['images']
|
||||
|
||||
+58
-26
@@ -1,12 +1,17 @@
|
||||
import os, torch
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from .utils import easySave
|
||||
from .utils import easySave, get_sd_version
|
||||
from .adv_encode import advanced_encode
|
||||
from .controlnet import easyControlnet
|
||||
from .log import log_node_warn
|
||||
from ..layer_diffuse import LayerDiffuse
|
||||
from ..config import RESOURCES_DIR
|
||||
from nodes import CLIPTextEncode
|
||||
try:
|
||||
from comfy_extras.nodes_flux import FluxGuidance
|
||||
except:
|
||||
FluxGuidance = None
|
||||
|
||||
class easyXYPlot():
|
||||
|
||||
@@ -15,6 +20,7 @@ class easyXYPlot():
|
||||
self.y_node_type, self.y_type = sampler.safe_split(xyPlotData.get("y_axis"), ': ')
|
||||
self.x_values = xyPlotData.get("x_vals") if self.x_type != "None" else []
|
||||
self.y_values = xyPlotData.get("y_vals") if self.y_type != "None" else []
|
||||
self.custom_font = xyPlotData.get("custom_font")
|
||||
|
||||
self.grid_spacing = xyPlotData.get("grid_spacing")
|
||||
self.latent_id = 0
|
||||
@@ -54,7 +60,10 @@ class easyXYPlot():
|
||||
value_label = f"ControlNet {index + 1}"
|
||||
|
||||
if value_type in ['Lora', 'Checkpoint']:
|
||||
value_label = f"{os.path.basename(os.path.splitext(value.split(',')[0])[0])}"
|
||||
arr = value.split(',')
|
||||
model_name = os.path.basename(os.path.splitext(arr[0])[0])
|
||||
trigger_words = ' ' + arr[3] if value_type == 'Lora' and len(arr[3]) > 2 else ''
|
||||
value_label = f"{model_name}{trigger_words}"
|
||||
|
||||
if value_type in ["ModelMergeBlocks"]:
|
||||
if ":" in value:
|
||||
@@ -87,8 +96,10 @@ class easyXYPlot():
|
||||
return plot_image_vars, value_label
|
||||
|
||||
@staticmethod
|
||||
def get_font(font_size):
|
||||
return ImageFont.truetype(str(Path(os.path.join(RESOURCES_DIR, 'OpenSans-Medium.ttf'))), font_size)
|
||||
def get_font(font_size, font_path=None):
|
||||
if font_path is None:
|
||||
font_path = str(Path(os.path.join(RESOURCES_DIR, 'OpenSans-Medium.ttf')))
|
||||
return ImageFont.truetype(font_path, font_size)
|
||||
|
||||
@staticmethod
|
||||
def update_label(label, value, num_items):
|
||||
@@ -118,7 +129,7 @@ class easyXYPlot():
|
||||
return bg_width, bg_height, x_offset_initial, y_offset
|
||||
|
||||
def adjust_font_size(self, text, initial_font_size, label_width):
|
||||
font = self.get_font(initial_font_size)
|
||||
font = self.get_font(initial_font_size, self.custom_font)
|
||||
text_width = font.getbbox(text)
|
||||
if text_width and text_width[2]:
|
||||
text_width = text_width[2]
|
||||
@@ -146,7 +157,7 @@ class easyXYPlot():
|
||||
label_bg = Image.new('RGBA', (label_width, label_height), color=(255, 255, 255, 0))
|
||||
d = ImageDraw.Draw(label_bg)
|
||||
|
||||
font = self.get_font(font_size)
|
||||
font = self.get_font(font_size, self.custom_font)
|
||||
|
||||
# Check if text will fit, if not insert ellipsis and reduce text
|
||||
if self.textsize(d, text, font=font)[0] > label_width:
|
||||
@@ -184,6 +195,8 @@ class easyXYPlot():
|
||||
clip = clip if clip is not None else plot_image_vars["clip"]
|
||||
steps = plot_image_vars['steps'] if "steps" in plot_image_vars else 1
|
||||
|
||||
sd_version = get_sd_version(plot_image_vars['model'])
|
||||
|
||||
# 高级用法
|
||||
if plot_image_vars["x_node_type"] == "advanced" or plot_image_vars["y_node_type"] == "advanced":
|
||||
if self.x_type == "Seeds++ Batch" or self.y_type == "Seeds++ Batch":
|
||||
@@ -338,7 +351,7 @@ class easyXYPlot():
|
||||
clip = clip if clip is not None else plot_image_vars["clip"]
|
||||
|
||||
xy_values = x_value if self.x_type == "Lora" else y_value
|
||||
lora_name, lora_model_strength, lora_clip_strength = xy_values.split(",")
|
||||
lora_name, lora_model_strength, lora_clip_strength, _ = xy_values.split(",")
|
||||
lora_stack = [{"lora_name": lora_name, "model": model, "clip" :clip, "model_strength": float(lora_model_strength), "clip_strength": float(lora_clip_strength)}]
|
||||
if 'lora_stack' in plot_image_vars:
|
||||
lora_stack = lora_stack + plot_image_vars['lora_stack']
|
||||
@@ -352,11 +365,14 @@ class easyXYPlot():
|
||||
if self.x_type == 'Positive Prompt S/R' or self.y_type == 'Positive Prompt S/R':
|
||||
positive = x_value if self.x_type == "Positive Prompt S/R" else y_value
|
||||
|
||||
positive = advanced_encode(clip, positive,
|
||||
plot_image_vars['positive_token_normalization'],
|
||||
plot_image_vars['positive_weight_interpretation'],
|
||||
w_max=1.0,
|
||||
apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
if sd_version == 'flux':
|
||||
positive, = CLIPTextEncode().encode(clip, positive)
|
||||
else:
|
||||
positive = advanced_encode(clip, positive,
|
||||
plot_image_vars['positive_token_normalization'],
|
||||
plot_image_vars['positive_weight_interpretation'],
|
||||
w_max=1.0,
|
||||
apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
|
||||
# if "positive_cond" in plot_image_vars:
|
||||
# positive = positive + plot_image_vars["positive_cond"]
|
||||
@@ -365,11 +381,14 @@ class easyXYPlot():
|
||||
if self.x_type == 'Negative Prompt S/R' or self.y_type == 'Negative Prompt S/R':
|
||||
negative = x_value if self.x_type == "Negative Prompt S/R" else y_value
|
||||
|
||||
negative = advanced_encode(clip, negative,
|
||||
plot_image_vars['negative_token_normalization'],
|
||||
plot_image_vars['negative_weight_interpretation'],
|
||||
w_max=1.0,
|
||||
apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
if sd_version == 'flux':
|
||||
negative, = CLIPTextEncode().encode(clip, negative)
|
||||
else:
|
||||
negative = advanced_encode(clip, negative,
|
||||
plot_image_vars['negative_token_normalization'],
|
||||
plot_image_vars['negative_weight_interpretation'],
|
||||
w_max=1.0,
|
||||
apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
# if "negative_cond" in plot_image_vars:
|
||||
# negative = negative + plot_image_vars["negative_cond"]
|
||||
|
||||
@@ -388,6 +407,11 @@ class easyXYPlot():
|
||||
start_percent = item[3]
|
||||
end_percent = item[4]
|
||||
positive, negative = easyControlnet().apply(control_net_name, image, positive, negative, strength, start_percent, end_percent, None, 1)
|
||||
# Flux guidance
|
||||
if self.x_type == "Flux Guidance" or self.y_type == "Flux Guidance":
|
||||
positive = plot_image_vars["positive_cond"] if "positive" in plot_image_vars else None
|
||||
flux_guidance = float(x_value) if self.x_type == "Flux Guidance" else float(y_value)
|
||||
positive, = FluxGuidance().append(positive, flux_guidance)
|
||||
|
||||
# 简单用法
|
||||
if plot_image_vars["x_node_type"] == "loader" or plot_image_vars["y_node_type"] == "loader":
|
||||
@@ -407,15 +431,21 @@ class easyXYPlot():
|
||||
clip = clip.clone()
|
||||
clip.clip_layer(plot_image_vars['clip_skip'])
|
||||
|
||||
positive = advanced_encode(clip, plot_image_vars['positive'],
|
||||
plot_image_vars['positive_token_normalization'],
|
||||
plot_image_vars['positive_weight_interpretation'], w_max=1.0,
|
||||
apply_to_pooled="enable",a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
if sd_version == 'flux':
|
||||
positive, = CLIPTextEncode().encode(clip, positive)
|
||||
else:
|
||||
positive = advanced_encode(clip, plot_image_vars['positive'],
|
||||
plot_image_vars['positive_token_normalization'],
|
||||
plot_image_vars['positive_weight_interpretation'], w_max=1.0,
|
||||
apply_to_pooled="enable",a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
|
||||
negative = advanced_encode(clip, plot_image_vars['negative'],
|
||||
plot_image_vars['negative_token_normalization'],
|
||||
plot_image_vars['negative_weight_interpretation'], w_max=1.0,
|
||||
apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
if sd_version == 'flux':
|
||||
negative, = CLIPTextEncode().encode(clip, negative)
|
||||
else:
|
||||
negative = advanced_encode(clip, plot_image_vars['negative'],
|
||||
plot_image_vars['negative_token_normalization'],
|
||||
plot_image_vars['negative_weight_interpretation'], w_max=1.0,
|
||||
apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
|
||||
model = model if model is not None else plot_image_vars["model"]
|
||||
vae = vae if vae is not None else plot_image_vars["vae"]
|
||||
@@ -429,6 +459,8 @@ class easyXYPlot():
|
||||
scheduler = scheduler if scheduler is not None else plot_image_vars["scheduler"]
|
||||
denoise = denoise if denoise is not None else plot_image_vars["denoise"]
|
||||
|
||||
noise_device = plot_image_vars["noise_device"] if "noise_device" in plot_image_vars else 'cpu'
|
||||
|
||||
# LayerDiffuse
|
||||
layer_diffusion_method = plot_image_vars["layer_diffusion_method"] if "layer_diffusion_method" in plot_image_vars else None
|
||||
empty_samples = plot_image_vars["empty_samples"] if "empty_samples" in plot_image_vars else None
|
||||
@@ -448,7 +480,7 @@ class easyXYPlot():
|
||||
samples = self.sampler.common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, samples,
|
||||
denoise=denoise, disable_noise=disable_noise, preview_latent=preview_latent,
|
||||
start_step=start_step, last_step=last_step,
|
||||
force_full_denoise=force_full_denoise)
|
||||
force_full_denoise=force_full_denoise, noise_device=noise_device)
|
||||
|
||||
# Decode images and store
|
||||
latent = samples["samples"]
|
||||
|
||||
+1347
-89
File diff suppressed because it is too large
Load Diff
+60
-3
@@ -1,7 +1,9 @@
|
||||
import os
|
||||
import json
|
||||
import comfy
|
||||
import folder_paths
|
||||
from .config import RESOURCES_DIR
|
||||
from .libs.utils import getMetadata
|
||||
def load_preset(filename):
|
||||
path = os.path.join(RESOURCES_DIR, filename)
|
||||
path = os.path.abspath(path)
|
||||
@@ -120,6 +122,28 @@ class XYplot_CFG:
|
||||
values = generate_floats(batch_count, first_cfg, last_cfg)
|
||||
return ({"axis": axis, "values": values},) if values else (None,)
|
||||
|
||||
class XYplot_FluxGuidance:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"batch_count": ("INT", {"default": 3, "min": 0, "max": 50}),
|
||||
"first_guidance": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}),
|
||||
"last_guidance": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("X_Y",)
|
||||
RETURN_NAMES = ("X or Y",)
|
||||
FUNCTION = "xy_value"
|
||||
CATEGORY = "EasyUse/XY Inputs"
|
||||
|
||||
def xy_value(self, batch_count, first_guidance, last_guidance):
|
||||
axis = "advanced: Flux Guidance"
|
||||
values = generate_floats(batch_count, first_guidance, last_guidance)
|
||||
return ({"axis": axis, "values": values},) if values else (None,)
|
||||
|
||||
# Step Values
|
||||
class XYplot_Sampler_Scheduler:
|
||||
parameters = ["sampler", "scheduler", "sampler & scheduler"]
|
||||
@@ -525,7 +549,8 @@ class XYplot_Lora:
|
||||
inputs["required"][f"clip_str_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
|
||||
|
||||
inputs["optional"] = {
|
||||
"optional_lora_stack": ("LORA_STACK",)
|
||||
"optional_lora_stack": ("LORA_STACK",),
|
||||
"display_trigger_word": ("BOOLEAN", {"display_trigger_word": True, "tooltip": "Trigger words showing lora model pass through the model's metadata, but not necessarily accurately."}),
|
||||
}
|
||||
return inputs
|
||||
|
||||
@@ -535,7 +560,39 @@ class XYplot_Lora:
|
||||
|
||||
CATEGORY = "EasyUse/XY Inputs"
|
||||
|
||||
def xy_value(self, input_mode, lora_count, model_strength, clip_strength, **kwargs):
|
||||
def sort_tags_by_frequency(self, meta_tags):
|
||||
if meta_tags is None:
|
||||
return []
|
||||
if "ss_tag_frequency" in meta_tags:
|
||||
meta_tags = meta_tags["ss_tag_frequency"]
|
||||
meta_tags = json.loads(meta_tags)
|
||||
sorted_tags = {}
|
||||
for _, dataset in meta_tags.items():
|
||||
for tag, count in dataset.items():
|
||||
tag = str(tag).strip()
|
||||
if tag in sorted_tags:
|
||||
sorted_tags[tag] = sorted_tags[tag] + count
|
||||
else:
|
||||
sorted_tags[tag] = count
|
||||
# sort tags by training frequency. Most seen tags firsts
|
||||
sorted_tags = dict(sorted(sorted_tags.items(), key=lambda item: item[1], reverse=True))
|
||||
return list(sorted_tags.keys())
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_trigger_words(self, lora_name, display=False):
|
||||
if not display:
|
||||
return ""
|
||||
|
||||
file_path = folder_paths.get_full_path('loras', lora_name)
|
||||
if not file_path:
|
||||
return ''
|
||||
header = getMetadata(file_path)
|
||||
header_json = json.loads(header)
|
||||
meta = header_json["__metadata__"] if "__metadata__" in header_json else None
|
||||
tags = self.sort_tags_by_frequency(meta)
|
||||
return ' '+ tags[0] if len(tags) > 0 else ''
|
||||
def xy_value(self, input_mode, lora_count, model_strength, clip_strength, display_trigger_words=True, **kwargs):
|
||||
|
||||
axis = "advanced: Lora"
|
||||
# Extract values from kwargs
|
||||
@@ -550,7 +607,7 @@ class XYplot_Lora:
|
||||
clip_strs[i] = clip_strength
|
||||
|
||||
# Extend each sub-array with lora_stack if it's not None
|
||||
values = [lora.replace(',', '*')+','+str(model_str)+','+str(clip_str) for lora, model_str, clip_str
|
||||
values = [lora.replace(',', '*')+','+str(model_str)+','+str(clip_str) +',' + self.get_trigger_words(lora, display_trigger_words) for lora, model_str, clip_str
|
||||
in zip(loras, model_strs, clip_strs) if lora != "None"]
|
||||
|
||||
optional_lora_stack = kwargs.get("optional_lora_stack") if "optional_lora_stack" in kwargs else []
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
[project]
|
||||
name = "comfyui-easy-use"
|
||||
description = "To enhance the usability of ComfyUI, optimizations and integrations have been implemented for several commonly used nodes."
|
||||
version = "1.2.1"
|
||||
version = "1.2.5"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["diffusers>=0.25.0", "accelerate>=0.25.0", "clip_interrogator>=0.6.0", "sentencepiece", "lark-parser", "onnxruntime", "spandrel", "opencv-python"]
|
||||
dependencies = ["diffusers", "accelerate", "clip_interrogator>=0.6.0", "sentencepiece", "lark-parser", "onnxruntime", "spandrel", "opencv-python"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/yolain/ComfyUI-Easy-Use"
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
diffusers>=0.25.0
|
||||
accelerate>=0.25.0
|
||||
diffusers
|
||||
accelerate
|
||||
clip_interrogator>=0.6.0
|
||||
lark-parser
|
||||
onnxruntime
|
||||
|
||||
@@ -50,6 +50,9 @@ textarea{
|
||||
.comfy-modal button {
|
||||
border-width:1px;
|
||||
}
|
||||
.comfy-modal-content{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
dialog{
|
||||
@@ -7,7 +7,7 @@ import { $t } from '../common/i18n.js';
|
||||
import { findWidgetByName, toggleWidget, updateNodeHeight} from "../common/utils.js";
|
||||
|
||||
const seedNodes = ["easy seed", "easy latentNoisy", "easy wildcards", "easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "easy preSamplingSdTurbo", "easy preSamplingCascade", "easy preSamplingDynamicCFG", "easy preSamplingLayerDiffusion", "easy fullkSampler", "easy fullCascadeKSampler"]
|
||||
const loaderNodes = ["easy fullLoader", "easy a1111Loader", "easy comfyLoader", "easy hunyuanDiTLoader", "easy pixArtLoader"]
|
||||
const loaderNodes = ["easy fullLoader", "easy a1111Loader", "easy comfyLoader", "easy fluxLoader", "easy hunyuanDiTLoader", "easy pixArtLoader"]
|
||||
|
||||
function widgetLogic(node, widget) {
|
||||
if (widget.name === 'lora_name') {
|
||||
@@ -697,6 +697,7 @@ app.registerExtension({
|
||||
switch (node.comfyClass){
|
||||
case "easy fullLoader":
|
||||
case "easy a1111Loader":
|
||||
case "easy fluxLoader":
|
||||
case "easy comfyLoader":
|
||||
case "easy cascadeLoader":
|
||||
case "easy svdLoader":
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user